Merge "Log setting of password complexity."
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index 531436e..fd4b106 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -307,6 +307,41 @@
         }
     }
 
+    /**
+     * Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they
+     * match the {@code queryExpression} in given namespaces and schemaTypes which is set via
+     * {@link SearchSpec.Builder#addNamespace} and {@link SearchSpec.Builder#addSchema}.
+     *
+     * <p> An empty {@code queryExpression} matches all documents.
+     *
+     * <p> An empty set of namespaces or schemaTypes matches all namespaces or schemaTypes in
+     * the current database.
+     *
+     * @param queryExpression Query String to search.
+     * @param searchSpec Defines what and how to remove
+     * @param executor Executor on which to invoke the callback.
+     * @param callback Callback to receive errors resulting from removing the documents. If the
+     *                 operation succeeds, the callback will be invoked with {@code null}.
+     */
+    public void removeByQuery(@NonNull String queryExpression,
+            @NonNull SearchSpec searchSpec,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<AppSearchResult<Void>> callback) {
+        Objects.requireNonNull(queryExpression);
+        Objects.requireNonNull(searchSpec);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        try {
+            mService.removeByQuery(mDatabaseName, queryExpression, searchSpec.getBundle(),
+                    new IAppSearchResultCallback.Stub() {
+                        public void onResult(AppSearchResult result) {
+                            executor.execute(() -> callback.accept(result));
+                        }
+                    });
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     // TODO(b/162450968) port query() and SearchResults.java to platform.
-    // TODO(b/162450968) port removeByQuery() to platform.
 }
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
index 4a98102..62e60d7 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
@@ -119,13 +119,14 @@
      * @param databaseName The databaseName this query for.
      * @param queryExpression String to search for
      * @param searchSpecBundle SearchSpec bundle
-     * @param callback {@link AndroidFuture}&lt;{@link AppSearchResult}&lt;{@link SearchResults}&gt;&gt;
+     * @param callback {@link IAppSearchResultCallback#onResult} will be called with an
+     *     {@link AppSearchResult}&lt;{@link Void}&gt;.
      */
     void removeByQuery(
         in String databaseName,
         in String queryExpression,
         in Bundle searchSpecBundle,
-        in AndroidFuture<AppSearchResult> callback);
+        in IAppSearchResultCallback callback);
 
     /**
      * Creates and initializes AppSearchImpl for the calling app.
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 8269799..c9dd89c 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -225,21 +225,21 @@
                 @NonNull String databaseName,
                 @NonNull String queryExpression,
                 @NonNull Bundle searchSpecBundle,
-                @NonNull AndroidFuture<AppSearchResult> callback) {
+                @NonNull IAppSearchResultCallback callback) {
             Preconditions.checkNotNull(databaseName);
             Preconditions.checkNotNull(queryExpression);
             Preconditions.checkNotNull(searchSpecBundle);
-            Preconditions.checkNotNull(callback);
             int callingUid = Binder.getCallingUidOrThrow();
             int callingUserId = UserHandle.getUserId(callingUid);
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
                 databaseName = rewriteDatabaseNameWithUid(databaseName, callingUid);
-                impl.removeByQuery(databaseName, queryExpression, new SearchSpec(searchSpecBundle));
-                callback.complete(AppSearchResult.newSuccessfulResult(/*result= */null));
+                impl.removeByQuery(databaseName, queryExpression,
+                        new SearchSpec(searchSpecBundle));
+                invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
             } catch (Throwable t) {
-                callback.complete(throwableToFailedResult(t));
+                invokeCallbackOnError(callback, t);
             } finally {
                 Binder.restoreCallingIdentity(callingIdentity);
             }
diff --git a/core/api/current.txt b/core/api/current.txt
index d8cccbf..07f8395 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6968,6 +6968,7 @@
     method public boolean grantKeyPairToApp(@Nullable android.content.ComponentName, @NonNull String, @NonNull String);
     method public boolean hasCaCertInstalled(@Nullable android.content.ComponentName, byte[]);
     method public boolean hasGrantedPolicy(@NonNull android.content.ComponentName, int);
+    method public boolean hasKeyPair(@NonNull String);
     method public boolean hasLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName);
     method public boolean installCaCert(@Nullable android.content.ComponentName, byte[]);
     method public boolean installExistingPackage(@NonNull android.content.ComponentName, String);
diff --git a/core/java/android/annotation/RequiresFeature.java b/core/java/android/annotation/RequiresFeature.java
index fc93f03..08861d4 100644
--- a/core/java/android/annotation/RequiresFeature.java
+++ b/core/java/android/annotation/RequiresFeature.java
@@ -30,7 +30,6 @@
  * Denotes that the annotated element requires one or more device features. This
  * is used to auto-generate documentation.
  *
- * @see PackageManager#hasSystemFeature(String)
  * @hide
  */
 @Retention(SOURCE)
@@ -38,8 +37,16 @@
 public @interface RequiresFeature {
     /**
      * The name of the device feature that is required.
-     *
-     * @see PackageManager#hasSystemFeature(String)
      */
     String value();
+
+    /**
+     * Defines the name of the method that should be called to check whether the feature is
+     * available, using the same signature format as javadoc. The feature checking method can have
+     * multiple parameters, but the feature name parameter must be of type String and must also be
+     * the first String-type parameter.
+     * <p>
+     * By default, the enforcement is {@link PackageManager#hasSystemFeature(String)}.
+     */
+    String enforcement() default("android.content.pm.PackageManager#hasSystemFeature");
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 247d6f0..26784f2c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5370,6 +5370,27 @@
         }
     }
 
+    // STOPSHIP(b/174298501): clarify the expected return value following generateKeyPair call.
+    /**
+     * Called by a device or profile owner, or delegated certificate installer, to query whether a
+     * certificate and private key are installed under a given alias.
+     *
+     * @param alias The alias under which the key pair is installed.
+     * @return {@code true} if a key pair with this alias exists, {@code false} otherwise.
+     * @throws SecurityException if the caller is not a device or profile owner or a delegated
+     *         certificate installer.
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_CERT_INSTALL
+     */
+    public boolean hasKeyPair(@NonNull String alias) {
+        throwIfParentInstance("hasKeyPair");
+        try {
+            return mService.hasKeyPair(mContext.getPackageName(), alias);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Called by a device or profile owner, or delegated certificate installer, to generate a
      * new private/public key pair. If the device supports key generation via secure hardware,
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 37d3451..8be3cdc 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -185,6 +185,7 @@
             in byte[] certBuffer, in byte[] certChainBuffer, String alias, boolean requestAccess,
             boolean isUserSelectable);
     boolean removeKeyPair(in ComponentName who, in String callerPackage, String alias);
+    boolean hasKeyPair(in String callerPackage, in String alias);
     boolean generateKeyPair(in ComponentName who, in String callerPackage, in String algorithm,
             in ParcelableKeyGenParameterSpec keySpec,
             in int idAttestationFlags, out KeymasterCertificateChain attestationChain);
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 901494b..237a9f2 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -200,6 +200,21 @@
         internalPath = parcel.readString8();
     }
 
+    public VolumeInfo(VolumeInfo volumeInfo) {
+        this.id = volumeInfo.id;
+        this.type = volumeInfo.type;
+        this.disk = volumeInfo.disk;
+        this.partGuid = volumeInfo.partGuid;
+        this.mountFlags = volumeInfo.mountFlags;
+        this.mountUserId = volumeInfo.mountUserId;
+        this.state = volumeInfo.state;
+        this.fsType = volumeInfo.fsType;
+        this.fsUuid = volumeInfo.fsUuid;
+        this.fsLabel = volumeInfo.fsLabel;
+        this.path = volumeInfo.path;
+        this.internalPath = volumeInfo.internalPath;
+    }
+
     @UnsupportedAppUsage
     public static @NonNull String getEnvironmentForState(int state) {
         final String envState = sStateToEnvironment.get(state);
diff --git a/core/java/android/view/Gravity.java b/core/java/android/view/Gravity.java
index defa58e..c8bfd36 100644
--- a/core/java/android/view/Gravity.java
+++ b/core/java/android/view/Gravity.java
@@ -15,8 +15,13 @@
  */
 
 package android.view;
+
+import android.annotation.IntDef;
 import android.graphics.Rect;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Standard constants and tools for placing an object within a potentially
  * larger container.
@@ -122,6 +127,32 @@
      */
     public static final int RELATIVE_HORIZONTAL_GRAVITY_MASK = START | END;
 
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {
+        Gravity.FILL,
+        Gravity.FILL_HORIZONTAL,
+        Gravity.FILL_VERTICAL,
+        Gravity.START,
+        Gravity.END,
+        Gravity.LEFT,
+        Gravity.RIGHT,
+        Gravity.TOP,
+        Gravity.BOTTOM,
+        Gravity.CENTER,
+        Gravity.CENTER_HORIZONTAL,
+        Gravity.CENTER_VERTICAL,
+        Gravity.DISPLAY_CLIP_HORIZONTAL,
+        Gravity.DISPLAY_CLIP_VERTICAL,
+        Gravity.CLIP_HORIZONTAL,
+        Gravity.CLIP_VERTICAL,
+        Gravity.NO_GRAVITY
+    })
+    public @interface GravityFlags {}
+
     /**
      * Apply a gravity constant to an object. This supposes that the layout direction is LTR.
      * 
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 1a6eea5..a23b7e1 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -32,6 +32,7 @@
 import android.os.Bundle;
 import android.os.IRemoteCallback;
 import android.os.ParcelFileDescriptor;
+import android.service.attestation.ImpressionToken;
 import android.view.DisplayCutout;
 import android.view.IApplicationToken;
 import android.view.IAppTransitionAnimationSpecsFuture;
@@ -760,4 +761,23 @@
      * {@link android.content.pm.PackageManager#getHoldLockToken()}.
      */
     void holdLock(in IBinder token, in int durationMs);
+
+    /**
+     * Gets an array of support hashing algorithms that can be used to generate the hash of the
+     * screenshot. The String value of one algorithm should be used when requesting to generate
+     * the impression attestation token.
+     *
+     * @return a String array of supported hashing algorithms.
+     */
+    String[] getSupportedImpressionAlgorithms();
+
+    /**
+     * Validate the impression token was generated by the system. The impression token passed in
+     * should be the token generated when calling {@link IWindowSession#generateImpressionToken}
+     *
+     * @param impressionToken The token to verify that it was generated by the system.
+     * @return true if the token was generated by the system or false if the token cannot be
+     *         verified.
+     */
+    boolean verifyImpressionToken(in ImpressionToken impressionToken);
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 0089a85..cfdaf8c 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -22,6 +22,7 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Bundle;
+import android.service.attestation.ImpressionToken;
 import android.util.MergedConfiguration;
 import android.view.DisplayCutout;
 import android.view.InputChannel;
@@ -344,4 +345,16 @@
      *                     window, the system will try to find a new focus target.
      */
     void grantEmbeddedWindowFocus(IWindow window, in IBinder inputToken, boolean grantFocus);
+
+    /**
+     * Generates an impression token that can be used to validate whether specific content was on
+     * screen.
+     *
+     * @param window The token for the window where the view to attest is shown.
+     * @param boundsInWindow The size and position of the ads view in the window
+     * @param hashAlgorithm The String for the hashing algorithm to use based on values returned
+     *                      from {@link IWindowManager#getSupportedImpressionAlgorithms()}
+     */
+    ImpressionToken generateImpressionToken(IWindow window, in Rect boundsInWindow,
+            in String hashAlgorithm);
 }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index ef2153e..b9afbc9 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -104,6 +104,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
+import android.view.Gravity.GravityFlags;
 import android.view.View.OnApplyWindowInsetsListener;
 import android.view.WindowInsets.Side;
 import android.view.WindowInsets.Side.InsetsSide;
@@ -2572,6 +2573,7 @@
          *
          * @see Gravity
          */
+        @GravityFlags
         public int gravity;
 
         /**
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 0ed7ca7..673073e 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -24,6 +24,7 @@
 import android.graphics.Region;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.service.attestation.ImpressionToken;
 import android.util.Log;
 import android.util.MergedConfiguration;
 import android.window.ClientWindowFrames;
@@ -460,4 +461,10 @@
     public void grantEmbeddedWindowFocus(IWindow callingWindow, IBinder targetInputToken,
                                          boolean grantFocus) {
     }
+
+    @Override
+    public ImpressionToken generateImpressionToken(IWindow window, Rect boundsInWindow,
+            String hashAlgorithm) {
+        return null;
+    }
 }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index c3d3985..8d2c2d9 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -88,8 +88,10 @@
 import android.view.autofill.AutofillManager;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.Completable;
 import com.android.internal.inputmethod.InputMethodDebug;
 import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
+import com.android.internal.inputmethod.ResultCallbacks;
 import com.android.internal.inputmethod.StartInputFlags;
 import com.android.internal.inputmethod.StartInputReason;
 import com.android.internal.inputmethod.UnbindReason;
@@ -666,6 +668,7 @@
                     final int startInputReason =
                             nextFocusHasConnection ? WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION
                                     : WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
+                    final Completable.InputBindResult value = Completable.createInputBindResult();
                     mService.startInputOrWindowGainedFocus(
                             startInputReason, mClient,
                             focusedView.getWindowToken(), startInputFlags, softInputMode,
@@ -673,7 +676,9 @@
                             null,
                             null,
                             0 /* missingMethodFlags */,
-                            mCurRootView.mContext.getApplicationInfo().targetSdkVersion);
+                            mCurRootView.mContext.getApplicationInfo().targetSdkVersion,
+                            ResultCallbacks.of(value));
+                    Completable.getResult(value); // ignore the result
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
@@ -2039,10 +2044,13 @@
                 if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
                         + ic + " tba=" + tba + " startInputFlags="
                         + InputMethodDebug.startInputFlagsToString(startInputFlags));
-                res = mService.startInputOrWindowGainedFocus(
+                final Completable.InputBindResult value = Completable.createInputBindResult();
+                mService.startInputOrWindowGainedFocus(
                         startInputReason, mClient, windowGainingFocus, startInputFlags,
                         softInputMode, windowFlags, tba, servedContext, missingMethodFlags,
-                        view.getContext().getApplicationInfo().targetSdkVersion);
+                        view.getContext().getApplicationInfo().targetSdkVersion,
+                        ResultCallbacks.of(value));
+                res = Completable.getResult(value);
                 if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
                 if (res == null) {
                     Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
diff --git a/core/java/com/android/internal/inputmethod/CallbackUtils.java b/core/java/com/android/internal/inputmethod/CallbackUtils.java
new file mode 100644
index 0000000..ec67792
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/CallbackUtils.java
@@ -0,0 +1,52 @@
+/*
+ * 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.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.os.RemoteException;
+
+import com.android.internal.view.InputBindResult;
+
+import java.util.function.Supplier;
+
+/**
+ * Defines a set of helper methods to callback corresponding results in {@link ResultCallbacks}.
+ */
+public final class CallbackUtils {
+
+    /**
+     * Not intended to be instantiated.
+     */
+    private CallbackUtils() {
+    }
+
+    /**
+     * A utility method using given {@link IInputBindResultResultCallback} to callback the
+     * {@link InputBindResult}.
+     *
+     * @param callback {@link IInputBindResultResultCallback} to be called back.
+     * @param resultSupplier the supplier from which {@link InputBindResult} is provided.
+     */
+    @AnyThread
+    public static void onResult(@NonNull IInputBindResultResultCallback callback,
+            @NonNull Supplier<InputBindResult> resultSupplier) {
+        try {
+            callback.onResult(resultSupplier.get());
+        } catch (RemoteException ignored) { }
+    }
+}
diff --git a/core/java/com/android/internal/inputmethod/Completable.java b/core/java/com/android/internal/inputmethod/Completable.java
index d8d1a7d..b9e1cf0 100644
--- a/core/java/com/android/internal/inputmethod/Completable.java
+++ b/core/java/com/android/internal/inputmethod/Completable.java
@@ -124,6 +124,16 @@
                 return true;
             }
         }
+
+        /**
+         * Blocks the calling thread until this object becomes ready to return the value.
+         */
+        @AnyThread
+        public void await() {
+            try {
+                mLatch.await();
+            } catch (InterruptedException ignored) { }
+        }
     }
 
     /**
@@ -250,6 +260,13 @@
     }
 
     /**
+     * @return an instance of {@link Completable.InputBindResult}.
+     */
+    public static Completable.InputBindResult createInputBindResult() {
+        return new Completable.InputBindResult();
+    }
+
+    /**
      * Completable object of {@link java.lang.Boolean}.
      */
     public static final class Boolean extends Values<java.lang.Boolean> { }
@@ -278,6 +295,18 @@
             extends Values<com.android.internal.view.InputBindResult> { }
 
     /**
+     * Await the result by the {@link Completable.Values}.
+     *
+     * @return the result once {@link ValueBase#onComplete()}
+     */
+    @AnyThread
+    @Nullable
+    public static <T> T getResult(@NonNull Completable.Values<T> value) {
+        value.await();
+        return value.getValue();
+    }
+
+    /**
      * Await the result by the {@link Completable.Int}, and log it if there is no result after
      * given timeout.
      *
diff --git a/services/wifi/java/android/net/wifi/WifiApiServiceInfo.aidl b/core/java/com/android/internal/inputmethod/IInputBindResultResultCallback.aidl
similarity index 69%
copy from services/wifi/java/android/net/wifi/WifiApiServiceInfo.aidl
copy to core/java/com/android/internal/inputmethod/IInputBindResultResultCallback.aidl
index 45e4c69..b52b3b1 100644
--- a/services/wifi/java/android/net/wifi/WifiApiServiceInfo.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputBindResultResultCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package android.net.wifi;
+package com.android.internal.inputmethod;
 
-/** @hide */
-parcelable WifiApiServiceInfo {
-    String name;
-    IBinder binder;
-}
+import com.android.internal.view.InputBindResult;
+
+oneway interface IInputBindResultResultCallback {
+    void onResult(in InputBindResult result);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/inputmethod/ResultCallbacks.java b/core/java/com/android/internal/inputmethod/ResultCallbacks.java
index 7131284..c59dcf4 100644
--- a/core/java/com/android/internal/inputmethod/ResultCallbacks.java
+++ b/core/java/com/android/internal/inputmethod/ResultCallbacks.java
@@ -21,6 +21,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
+import com.android.internal.view.InputBindResult;
+
 import java.lang.ref.WeakReference;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -154,4 +156,31 @@
             }
         };
     }
+
+    /**
+     * Creates {@link IInputBindResultResultCallback.Stub} that is to set
+     * {@link Completable.InputBindResult} when receiving the result.
+     *
+     * @param value {@link Completable.InputBindResult} to be set when receiving the result.
+     * @return {@link IInputBindResultResultCallback.Stub} that can be passed as a binder IPC
+     *         parameter.
+     */
+    @AnyThread
+    public static IInputBindResultResultCallback.Stub of(
+            @NonNull Completable.InputBindResult value) {
+        final AtomicReference<WeakReference<Completable.InputBindResult>>
+                atomicRef = new AtomicReference<>(new WeakReference<>(value));
+
+        return new IInputBindResultResultCallback.Stub() {
+            @BinderThread
+            @Override
+            public void onResult(InputBindResult result) {
+                final Completable.InputBindResult value = unwrap(atomicRef);
+                if (value == null) {
+                    return;
+                }
+                value.onComplete(result);
+            }
+        };
+    }
 }
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 33abbe8..e78ed4e 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -24,6 +24,7 @@
 import com.android.internal.view.InputBindResult;
 import com.android.internal.view.IInputContext;
 import com.android.internal.view.IInputMethodClient;
+import com.android.internal.inputmethod.IInputBindResultResultCallback;
 
 /**
  * Public interface to the global input method manager, used by all client
@@ -48,14 +49,15 @@
     // If windowToken is null, this just does startInput().  Otherwise this reports that a window
     // has gained focus, and if 'attribute' is non-null then also does startInput.
     // @NonNull
-    InputBindResult startInputOrWindowGainedFocus(
+    void startInputOrWindowGainedFocus(
             /* @StartInputReason */ int startInputReason,
             in IInputMethodClient client, in IBinder windowToken,
             /* @StartInputFlags */ int startInputFlags,
             /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
             int windowFlags, in EditorInfo attribute, IInputContext inputContext,
             /* @InputConnectionInspector.MissingMethodFlags */ int missingMethodFlags,
-            int unverifiedTargetSdkVersion);
+            int unverifiedTargetSdkVersion,
+            in IInputBindResultResultCallback inputBindResult);
 
     void showInputMethodPickerFromClient(in IInputMethodClient client,
             int auxiliarySubtypeMode);
diff --git a/core/proto/android/view/windowlayoutparams.proto b/core/proto/android/view/windowlayoutparams.proto
index 4bb56f8..062485d 100644
--- a/core/proto/android/view/windowlayoutparams.proto
+++ b/core/proto/android/view/windowlayoutparams.proto
@@ -35,7 +35,7 @@
     optional int32 height = 5;
     optional float horizontal_margin = 6;
     optional float vertical_margin = 7;
-    optional int32 gravity = 8;
+    optional int32 gravity = 8 [(.android.typedef) = "android.view.Gravity.GravityFlags"];
     optional int32 soft_input_mode = 9 [(.android.typedef) = "android.view.WindowManager.LayoutParams.SoftInputModeFlags"];
     optional .android.graphics.PixelFormatProto.Format format = 10;
     optional int32 window_animations = 11;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index db80287..5a69056 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -120,6 +120,12 @@
     <protected-broadcast android:name="android.app.action.EXIT_DESK_MODE" />
     <protected-broadcast android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
 
+    <protected-broadcast android:name="android.app.action.USER_ADDED" />
+    <protected-broadcast android:name="android.app.action.USER_REMOVED" />
+    <protected-broadcast android:name="android.app.action.USER_STARTED" />
+    <protected-broadcast android:name="android.app.action.USER_STOPPED" />
+    <protected-broadcast android:name="android.app.action.USER_SWITCHED" />
+
     <protected-broadcast android:name="android.app.action.BUGREPORT_SHARING_DECLINED" />
     <protected-broadcast android:name="android.app.action.BUGREPORT_FAILED" />
     <protected-broadcast android:name="android.app.action.BUGREPORT_SHARE" />
@@ -3104,6 +3110,12 @@
     <permission android:name="android.permission.DUMP"
         android:protectionLevel="signature|privileged|development" />
 
+    <!-- Allows an application to start tracing for InputMethod and WindowManager.
+    <p>Not for use by third-party applications.
+    @hide -->
+    <permission android:name="android.permission.CONTROL_UI_TRACING"
+        android:protectionLevel="signature|privileged|development" />
+
     <!-- Allows an application to read the low-level system log files.
     <p>Not for use by third-party applications, because
     Log entries can contain the user's private information. -->
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 8406fdf..1fb63f0 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -322,6 +322,7 @@
         <permission name="android.permission.DELETE_CACHE_FILES"/>
         <permission name="android.permission.DELETE_PACKAGES"/>
         <permission name="android.permission.DUMP"/>
+        <permission name="android.permission.CONTROL_UI_TRACING"/>
         <permission name="android.permission.ACTIVITY_EMBEDDING"/>
         <permission name="android.permission.FORCE_STOP_PACKAGES"/>
         <permission name="android.permission.GET_APP_OPS_STATS"/>
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index 97da3cc..1ae6a63 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -46,6 +46,7 @@
     boolean installKeyPair(
         in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias, int uid);
     boolean removeKeyPair(String alias);
+    boolean containsKeyPair(String alias);
 
     // APIs used by Settings
     boolean deleteCaCertificate(String alias);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java
index 061d3f8..6e87f13 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java
@@ -107,6 +107,7 @@
         transaction.setWindowCrop(leash, crop);
     }
 
+    // TODO(b/173440321): Correct presentation of letterboxed activities in One-handed mode.
     private void resolveTaskPositionAndCrop(
                 ActivityManager.RunningTaskInfo taskInfo,
                 Point positionInParent,
@@ -125,15 +126,18 @@
         final Rect activityBounds = taskInfo.letterboxActivityBounds;
 
         Insets insets = getInsets();
+        Rect displayBoundsWithInsets =
+                new Rect(mWindowManager.getMaximumWindowMetrics().getBounds());
+        displayBoundsWithInsets.inset(insets);
 
         Rect taskBoundsWithInsets = new Rect(taskBounds);
-        applyInsets(taskBoundsWithInsets, insets, taskInfo.parentBounds);
+        taskBoundsWithInsets.intersect(displayBoundsWithInsets);
 
         Rect activityBoundsWithInsets = new Rect(activityBounds);
-        applyInsets(activityBoundsWithInsets, insets, taskInfo.parentBounds);
+        activityBoundsWithInsets.intersect(displayBoundsWithInsets);
 
         Rect parentBoundsWithInsets = new Rect(parentBounds);
-        applyInsets(parentBoundsWithInsets, insets, parentBounds);
+        parentBoundsWithInsets.intersect(displayBoundsWithInsets);
 
         // Crop need to be in the task coordinates.
         crop.set(activityBoundsWithInsets);
@@ -217,10 +221,4 @@
                                 | WindowInsets.Type.displayCutout());
     }
 
-    private void applyInsets(Rect innerBounds, Insets insets, Rect outerBounds) {
-        Rect outerBoundsWithInsets = new Rect(outerBounds);
-        outerBoundsWithInsets.inset(insets);
-        innerBounds.intersect(outerBoundsWithInsets);
-    }
-
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxTaskListenerTest.java
index 0f719af..fc0e20b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxTaskListenerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxTaskListenerTest.java
@@ -96,6 +96,7 @@
         mLetterboxTaskListener.onTaskAppeared(
                 createTaskInfo(
                         /* taskId */ 1,
+                        /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
                         /* parentBounds */ new Rect(0, 0, 200, 100),
                         /* activityBounds */ new Rect(75, 0, 125, 75),
                         /* taskBounds */ new Rect(50, 0, 125, 100)),
@@ -109,6 +110,7 @@
         mLetterboxTaskListener.onTaskInfoChanged(
                 createTaskInfo(
                         /* taskId */ 1,
+                        /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
                         /* parentBounds */ new Rect(0, 0, 200, 100),
                         // Activity is offset by 25 to the left
                         /* activityBounds */ new Rect(50, 0, 100, 75),
@@ -130,6 +132,7 @@
         mLetterboxTaskListener.onTaskAppeared(
                 createTaskInfo(
                         /* taskId */ 1,
+                        /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
                         /* parentBounds */ new Rect(0, 0, 200, 100),
                         /* activityBounds */ new Rect(150, 0, 200, 75),
                         /* taskBounds */ new Rect(125, 0, 200, 100)),
@@ -150,6 +153,7 @@
         mLetterboxTaskListener.onTaskAppeared(
                 createTaskInfo(
                         /* taskId */ 1,
+                        /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
                         /* parentBounds */ new Rect(0, 0, 200, 100),
                         /* activityBounds */ new Rect(150, 0, 200, 75),
                         /* taskBounds */ new Rect(125, 0, 200, 100)),
@@ -170,6 +174,7 @@
         mLetterboxTaskListener.onTaskAppeared(
                 createTaskInfo(
                         /* taskId */ 1,
+                        /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds
                         /* parentBounds */ new Rect(0, 0, 200, 100),
                         /* activityBounds */ new Rect(50, 0, 100, 75),
                         /* taskBounds */ new Rect(25, 0, 100, 100)),
@@ -190,6 +195,7 @@
         mLetterboxTaskListener.onTaskAppeared(
                 createTaskInfo(
                         /* taskId */ 1,
+                        /* maxBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds
                         /* parentBounds */ new Rect(0, 0, 100, 150),
                         /* activityBounds */ new Rect(0, 75, 50, 125),
                         /* taskBounds */ new Rect(0, 50, 100, 125)),
@@ -210,6 +216,7 @@
         mLetterboxTaskListener.onTaskAppeared(
                 createTaskInfo(
                         /* taskId */ 1,
+                        /* maxBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds
                         /* parentBounds */ new Rect(0, 0, 100, 150),
                         /* activityBounds */ new Rect(0, 75, 50, 125),
                         /* taskBounds */ new Rect(0, 50, 100, 125)),
@@ -230,6 +237,7 @@
         mLetterboxTaskListener.onTaskAppeared(
                 createTaskInfo(
                         /* taskId */ 1,
+                        /* maxBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds
                         /* parentBounds */ new Rect(0, 0, 100, 150),
                         /* activityBounds */ new Rect(0, 75, 50, 125),
                         /* taskBounds */ new Rect(0, 50, 100, 125)),
@@ -250,6 +258,7 @@
         mLetterboxTaskListener.onTaskAppeared(
                 createTaskInfo(
                         /* taskId */ 1,
+                        /* maxBounds= */ new Rect(0, 0, 200, 125), // equal to parent bounds
                         /* parentBounds */ new Rect(0, 0, 200, 125),
                         /* activityBounds */ new Rect(15, 0, 175, 120),
                         /* taskBounds */ new Rect(0, 0, 100, 125)), // equal to parent bounds
@@ -272,6 +281,7 @@
         mLetterboxTaskListener.onTaskAppeared(
                 createTaskInfo(
                         /* taskId */ 1,
+                        /* maxBounds= */ new Rect(0, 0, 100, 150),
                         /* parentBounds */ new Rect(0, 75, 100, 225),
                         /* activityBounds */ new Rect(25, 75, 75, 125),
                         /* taskBounds */ new Rect(0, 75, 100, 125)),
@@ -285,7 +295,7 @@
     public void testOnTaskAppeared_calledSecondTimeWithSameTaskId_throwsException() {
         setWindowBoundsAndInsets(new Rect(),  Insets.NONE);
         RunningTaskInfo taskInfo =
-                createTaskInfo(/* taskId */ 1, new Rect(), new Rect(), new Rect());
+                createTaskInfo(/* taskId */ 1, new Rect(),  new Rect(), new Rect(), new Rect());
         mLetterboxTaskListener.onTaskAppeared(taskInfo, mLeash);
         mLetterboxTaskListener.onTaskAppeared(taskInfo, mLeash);
     }
@@ -306,11 +316,13 @@
 
     private static RunningTaskInfo createTaskInfo(
                 int taskId,
+                final Rect maxBounds,
                 final Rect parentBounds,
                 final Rect activityBounds,
                 final Rect taskBounds) {
         RunningTaskInfo taskInfo = new RunningTaskInfo();
         taskInfo.taskId = taskId;
+        taskInfo.configuration.windowConfiguration.setMaxBounds(maxBounds);
         taskInfo.parentBounds = parentBounds;
         taskInfo.configuration.windowConfiguration.setBounds(taskBounds);
         taskInfo.letterboxActivityBounds = Rect.copyOrNull(activityBounds);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 4ed5457..cd53217 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -429,6 +429,7 @@
     whole_static_libs: ["libskia"],
 
     srcs: [
+        "canvas/CanvasFrontend.cpp",
         "canvas/CanvasOpBuffer.cpp",
         "canvas/CanvasOpRasterizer.cpp",
         "pipeline/skia/SkiaDisplayList.cpp",
@@ -607,6 +608,7 @@
         "tests/unit/CacheManagerTests.cpp",
         "tests/unit/CanvasContextTests.cpp",
         "tests/unit/CanvasOpTests.cpp",
+        "tests/unit/CanvasFrontendTests.cpp",
         "tests/unit/CommonPoolTests.cpp",
         "tests/unit/DamageAccumulatorTests.cpp",
         "tests/unit/DeferredLayerUpdaterTests.cpp",
diff --git a/libs/hwui/SaveFlags.h b/libs/hwui/SaveFlags.h
new file mode 100644
index 0000000..f3579a8
--- /dev/null
+++ b/libs/hwui/SaveFlags.h
@@ -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.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+
+// TODO: Move this to an enum class
+namespace android::SaveFlags {
+
+// These must match the corresponding Canvas API constants.
+enum {
+    Matrix = 0x01,
+    Clip = 0x02,
+    HasAlphaLayer = 0x04,
+    ClipToLayer = 0x10,
+
+    // Helper constant
+    MatrixClip = Matrix | Clip,
+};
+typedef uint32_t Flags;
+
+}  // namespace android::SaveFlags
diff --git a/libs/hwui/canvas/CanvasFrontend.cpp b/libs/hwui/canvas/CanvasFrontend.cpp
new file mode 100644
index 0000000..2c839b0
--- /dev/null
+++ b/libs/hwui/canvas/CanvasFrontend.cpp
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+#include "CanvasFrontend.h"
+#include "CanvasOps.h"
+#include "CanvasOpBuffer.h"
+
+namespace android::uirenderer {
+
+CanvasStateHelper::CanvasStateHelper(int width, int height) {
+    mInitialBounds = SkIRect::MakeWH(width, height);
+    mSaveStack.emplace_back();
+    mClipStack.emplace_back().setRect(mInitialBounds);
+    mTransformStack.emplace_back();
+    mCurrentClipIndex = 0;
+    mCurrentTransformIndex = 0;
+}
+
+bool CanvasStateHelper::internalSave(SaveEntry saveEntry) {
+    mSaveStack.push_back(saveEntry);
+    if (saveEntry.matrix) {
+        // We need to push before accessing transform() to ensure the reference doesn't move
+        // across vector resizes
+        mTransformStack.emplace_back() = transform();
+        mCurrentTransformIndex += 1;
+    }
+    if (saveEntry.clip) {
+        // We need to push before accessing clip() to ensure the reference doesn't move
+        // across vector resizes
+        mClipStack.emplace_back() = clip();
+        mCurrentClipIndex += 1;
+        return true;
+    }
+    return false;
+}
+
+// Assert that the cast from SkClipOp to SkRegion::Op is valid
+static_assert(static_cast<int>(SkClipOp::kDifference) == SkRegion::Op::kDifference_Op);
+static_assert(static_cast<int>(SkClipOp::kIntersect) == SkRegion::Op::kIntersect_Op);
+static_assert(static_cast<int>(SkClipOp::kUnion_deprecated) == SkRegion::Op::kUnion_Op);
+static_assert(static_cast<int>(SkClipOp::kXOR_deprecated) == SkRegion::Op::kXOR_Op);
+static_assert(static_cast<int>(SkClipOp::kReverseDifference_deprecated) == SkRegion::Op::kReverseDifference_Op);
+static_assert(static_cast<int>(SkClipOp::kReplace_deprecated) == SkRegion::Op::kReplace_Op);
+
+void CanvasStateHelper::internalClipRect(const SkRect& rect, SkClipOp op) {
+    clip().opRect(rect, transform(), mInitialBounds, (SkRegion::Op)op, false);
+}
+
+void CanvasStateHelper::internalClipPath(const SkPath& path, SkClipOp op) {
+    clip().opPath(path, transform(), mInitialBounds, (SkRegion::Op)op, true);
+}
+
+bool CanvasStateHelper::internalRestore() {
+    // Prevent underflows
+    if (saveCount() <= 1) {
+        return false;
+    }
+
+    SaveEntry entry = mSaveStack[mSaveStack.size() - 1];
+    mSaveStack.pop_back();
+    bool needsRestorePropagation = entry.layer;
+    if (entry.matrix) {
+        mTransformStack.pop_back();
+        mCurrentTransformIndex -= 1;
+    }
+    if (entry.clip) {
+        // We need to push before accessing clip() to ensure the reference doesn't move
+        // across vector resizes
+        mClipStack.pop_back();
+        mCurrentClipIndex -= 1;
+        needsRestorePropagation = true;
+    }
+    return needsRestorePropagation;
+}
+
+SkRect CanvasStateHelper::getClipBounds() const {
+    SkIRect ibounds = clip().getBounds();
+
+    if (ibounds.isEmpty()) {
+        return SkRect::MakeEmpty();
+    }
+
+    SkMatrix inverse;
+    // if we can't invert the CTM, we can't return local clip bounds
+    if (!transform().invert(&inverse)) {
+        return SkRect::MakeEmpty();
+    }
+
+    SkRect ret = SkRect::MakeEmpty();
+    inverse.mapRect(&ret, SkRect::Make(ibounds));
+    return ret;
+}
+
+bool CanvasStateHelper::quickRejectRect(float left, float top, float right, float bottom) const {
+    // TODO: Implement
+    return false;
+}
+
+bool CanvasStateHelper::quickRejectPath(const SkPath& path) const {
+    // TODO: Implement
+    return false;
+}
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/canvas/CanvasFrontend.h b/libs/hwui/canvas/CanvasFrontend.h
new file mode 100644
index 0000000..5fccccb
--- /dev/null
+++ b/libs/hwui/canvas/CanvasFrontend.h
@@ -0,0 +1,200 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+// TODO: Can we get the dependencies scoped down more?
+#include "CanvasOps.h"
+#include "CanvasOpBuffer.h"
+#include <SaveFlags.h>
+
+#include <SkRasterClip.h>
+#include <ui/FatVector.h>
+
+#include <optional>
+
+namespace android::uirenderer {
+
+// Exists to avoid forcing all this common logic into the templated class
+class CanvasStateHelper {
+protected:
+    CanvasStateHelper(int width, int height);
+    ~CanvasStateHelper() = default;
+
+    struct SaveEntry {
+        bool clip : 1 = false;
+        bool matrix : 1 = false;
+        bool layer : 1 = false;
+    };
+
+    constexpr SaveEntry saveEntryForLayer() {
+        return {
+            .clip = true,
+            .matrix = true,
+            .layer = true,
+        };
+    }
+
+    constexpr SaveEntry flagsToSaveEntry(SaveFlags::Flags flags) {
+        return SaveEntry {
+            .clip = static_cast<bool>(flags & SaveFlags::Clip),
+            .matrix = static_cast<bool>(flags & SaveFlags::Matrix),
+            .layer = false
+        };
+    }
+
+    bool internalSave(SaveEntry saveEntry);
+    bool internalSave(SaveFlags::Flags flags) {
+        return internalSave(flagsToSaveEntry(flags));
+    }
+    void internalSaveLayer(const SkCanvas::SaveLayerRec& layerRec) {
+        internalSave({
+            .clip = true,
+            .matrix = true,
+            .layer = true
+        });
+        internalClipRect(*layerRec.fBounds, SkClipOp::kIntersect);
+    }
+
+    bool internalRestore();
+
+    void internalClipRect(const SkRect& rect, SkClipOp op);
+    void internalClipPath(const SkPath& path, SkClipOp op);
+
+    SkIRect mInitialBounds;
+    FatVector<SaveEntry, 6> mSaveStack;
+    FatVector<SkMatrix, 6> mTransformStack;
+    FatVector<SkConservativeClip, 6> mClipStack;
+
+    size_t mCurrentTransformIndex;
+    size_t mCurrentClipIndex;
+
+    const SkConservativeClip& clip() const {
+        return mClipStack[mCurrentClipIndex];
+    }
+
+    SkConservativeClip& clip() {
+        return mClipStack[mCurrentClipIndex];
+    }
+
+public:
+    int saveCount() const { return mSaveStack.size(); }
+
+    SkRect getClipBounds() const;
+    bool quickRejectRect(float left, float top, float right, float bottom) const;
+    bool quickRejectPath(const SkPath& path) const;
+
+    const SkMatrix& transform() const {
+        return mTransformStack[mCurrentTransformIndex];
+    }
+
+    SkMatrix& transform() {
+        return mTransformStack[mCurrentTransformIndex];
+    }
+
+    // For compat with existing HWUI Canvas interface
+    void getMatrix(SkMatrix* outMatrix) const {
+        *outMatrix = transform();
+    }
+
+    void setMatrix(const SkMatrix& matrix) {
+        transform() = matrix;
+    }
+
+    void concat(const SkMatrix& matrix) {
+        transform().preConcat(matrix);
+    }
+
+    void rotate(float degrees) {
+        SkMatrix m;
+        m.setRotate(degrees);
+        concat(m);
+    }
+
+    void scale(float sx, float sy) {
+        SkMatrix m;
+        m.setScale(sx, sy);
+        concat(m);
+    }
+
+    void skew(float sx, float sy) {
+        SkMatrix m;
+        m.setSkew(sx, sy);
+        concat(m);
+    }
+
+    void translate(float dx, float dy) {
+        transform().preTranslate(dx, dy);
+    }
+};
+
+// Front-end canvas that handles queries, up-front state, and produces CanvasOp<> output downstream
+template <typename CanvasOpReceiver>
+class CanvasFrontend final : public CanvasStateHelper {
+public:
+    template<class... Args>
+    CanvasFrontend(int width, int height, Args&&... args) : CanvasStateHelper(width, height),
+            mReceiver(std::forward<Args>(args)...) { }
+    ~CanvasFrontend() = default;
+
+    void save(SaveFlags::Flags flags = SaveFlags::MatrixClip) {
+        if (internalSave(flagsToSaveEntry(flags))) {
+            submit<CanvasOpType::Save>({});
+        }
+    }
+
+    void restore() {
+        if (internalRestore()) {
+            submit<CanvasOpType::Restore>({});
+        }
+    }
+
+    template <CanvasOpType T>
+    void draw(CanvasOp<T>&& op) {
+        // The front-end requires going through certain front-doors, which these aren't.
+        static_assert(T != CanvasOpType::Save, "Must use CanvasFrontend::save() call instead");
+        static_assert(T != CanvasOpType::Restore, "Must use CanvasFrontend::restore() call instead");
+
+        if constexpr (T == CanvasOpType::SaveLayer) {
+            internalSaveLayer(op.saveLayerRec);
+        }
+        if constexpr (T == CanvasOpType::SaveBehind) {
+            // Don't use internalSaveLayer as this doesn't apply clipping, it's a "regular" save
+            // But we do want to flag it as a layer, such that restore is Definitely Required
+            internalSave(saveEntryForLayer());
+        }
+        if constexpr (T == CanvasOpType::ClipRect) {
+            internalClipRect(op.rect, op.op);
+        }
+        if constexpr (T == CanvasOpType::ClipPath) {
+            internalClipPath(op.path, op.op);
+        }
+
+        submit(std::move(op));
+    }
+
+    const CanvasOpReceiver& receiver() const { return mReceiver; }
+
+private:
+    CanvasOpReceiver mReceiver;
+
+    template <CanvasOpType T>
+    void submit(CanvasOp<T>&& op) {
+        mReceiver.push_container(CanvasOpContainer(std::move(op), transform()));
+    }
+};
+
+} // namespace android::uirenderer
diff --git a/services/wifi/java/android/net/wifi/WifiApiServiceInfo.aidl b/libs/hwui/canvas/CanvasOpRecorder.cpp
similarity index 72%
rename from services/wifi/java/android/net/wifi/WifiApiServiceInfo.aidl
rename to libs/hwui/canvas/CanvasOpRecorder.cpp
index 45e4c69..bb968ee8 100644
--- a/services/wifi/java/android/net/wifi/WifiApiServiceInfo.aidl
+++ b/libs/hwui/canvas/CanvasOpRecorder.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package android.net.wifi;
+#include "CanvasOpRecorder.h"
 
-/** @hide */
-parcelable WifiApiServiceInfo {
-    String name;
-    IBinder binder;
-}
+#include "CanvasOpBuffer.h"
+#include "CanvasOps.h"
+
+namespace android::uirenderer {}  // namespace android::uirenderer
diff --git a/libs/hwui/canvas/CanvasOpRecorder.h b/libs/hwui/canvas/CanvasOpRecorder.h
new file mode 100644
index 0000000..7d95bc4
--- /dev/null
+++ b/libs/hwui/canvas/CanvasOpRecorder.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "hwui/Canvas.h"
+#include "CanvasOpBuffer.h"
+
+#include <vector>
+
+namespace android::uirenderer {
+
+// Interop with existing HWUI Canvas
+class CanvasOpRecorder final : /* todo: public Canvas */ {
+public:
+    // Transform ops
+private:
+    struct SaveEntry {
+
+    };
+
+    std::vector<SaveEntry> mSaveStack;
+};
+
+}  // namespace android::uirenderer
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index f94bae274..4d67166 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -18,6 +18,7 @@
 
 #include <cutils/compiler.h>
 #include <utils/Functor.h>
+#include <SaveFlags.h>
 
 #include <androidfw/ResourceTypes.h>
 #include "Properties.h"
@@ -57,22 +58,6 @@
 using DisplayList = skiapipeline::SkiaDisplayList;
 }
 
-namespace SaveFlags {
-
-// These must match the corresponding Canvas API constants.
-enum {
-    Matrix = 0x01,
-    Clip = 0x02,
-    HasAlphaLayer = 0x04,
-    ClipToLayer = 0x10,
-
-    // Helper constant
-    MatrixClip = Matrix | Clip,
-};
-typedef uint32_t Flags;
-
-}  // namespace SaveFlags
-
 namespace uirenderer {
 namespace VectorDrawable {
 class Tree;
diff --git a/libs/hwui/tests/unit/CanvasFrontendTests.cpp b/libs/hwui/tests/unit/CanvasFrontendTests.cpp
new file mode 100644
index 0000000..05b1179
--- /dev/null
+++ b/libs/hwui/tests/unit/CanvasFrontendTests.cpp
@@ -0,0 +1,213 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <canvas/CanvasFrontend.h>
+#include <canvas/CanvasOpBuffer.h>
+#include <canvas/CanvasOps.h>
+#include <canvas/CanvasOpRasterizer.h>
+
+#include <tests/common/CallCountingCanvas.h>
+
+#include "SkPictureRecorder.h"
+#include "SkColor.h"
+#include "SkLatticeIter.h"
+#include "pipeline/skia/AnimatedDrawables.h"
+#include <SkNoDrawCanvas.h>
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::test;
+
+class CanvasOpCountingReceiver {
+public:
+    template <CanvasOpType T>
+    void push_container(CanvasOpContainer<T>&& op) {
+        mOpCounts[static_cast<size_t>(T)] += 1;
+    }
+
+    int operator[](CanvasOpType op) const {
+        return mOpCounts[static_cast<size_t>(op)];
+    }
+
+private:
+    std::array<int, static_cast<size_t>(CanvasOpType::COUNT)> mOpCounts;
+};
+
+TEST(CanvasFrontend, saveCount) {
+    SkNoDrawCanvas skiaCanvas(100, 100);
+    CanvasFrontend<CanvasOpCountingReceiver> opCanvas(100, 100);
+    const auto& receiver = opCanvas.receiver();
+
+    EXPECT_EQ(1, skiaCanvas.getSaveCount());
+    EXPECT_EQ(1, opCanvas.saveCount());
+
+    skiaCanvas.save();
+    opCanvas.save(SaveFlags::MatrixClip);
+    EXPECT_EQ(2, skiaCanvas.getSaveCount());
+    EXPECT_EQ(2, opCanvas.saveCount());
+
+    skiaCanvas.restore();
+    opCanvas.restore();
+    EXPECT_EQ(1, skiaCanvas.getSaveCount());
+    EXPECT_EQ(1, opCanvas.saveCount());
+
+    skiaCanvas.restore();
+    opCanvas.restore();
+    EXPECT_EQ(1, skiaCanvas.getSaveCount());
+    EXPECT_EQ(1, opCanvas.saveCount());
+
+    EXPECT_EQ(1, receiver[CanvasOpType::Save]);
+    EXPECT_EQ(1, receiver[CanvasOpType::Restore]);
+}
+
+TEST(CanvasFrontend, transform) {
+    SkNoDrawCanvas skiaCanvas(100, 100);
+    CanvasFrontend<CanvasOpCountingReceiver> opCanvas(100, 100);
+
+    skiaCanvas.translate(10, 10);
+    opCanvas.translate(10, 10);
+    EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
+
+    {
+        skiaCanvas.save();
+        opCanvas.save(SaveFlags::Matrix);
+        skiaCanvas.scale(2.0f, 1.125f);
+        opCanvas.scale(2.0f, 1.125f);
+
+        EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
+        skiaCanvas.restore();
+        opCanvas.restore();
+    }
+
+    EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
+
+    {
+        skiaCanvas.save();
+        opCanvas.save(SaveFlags::Matrix);
+        skiaCanvas.rotate(90.f);
+        opCanvas.rotate(90.f);
+
+        EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
+
+        {
+            skiaCanvas.save();
+            opCanvas.save(SaveFlags::Matrix);
+            skiaCanvas.skew(5.0f, 2.25f);
+            opCanvas.skew(5.0f, 2.25f);
+
+            EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
+            skiaCanvas.restore();
+            opCanvas.restore();
+        }
+
+        skiaCanvas.restore();
+        opCanvas.restore();
+    }
+
+    EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
+}
+
+TEST(CanvasFrontend, drawOpTransform) {
+    CanvasFrontend<CanvasOpBuffer> opCanvas(100, 100);
+    const auto& receiver = opCanvas.receiver();
+
+    auto makeDrawRect = [] {
+        return CanvasOp<CanvasOpType::DrawRect>{
+            .rect = SkRect::MakeWH(50, 50),
+            .paint = SkPaint(SkColors::kBlack),
+        };
+    };
+
+    opCanvas.draw(makeDrawRect());
+
+    opCanvas.translate(10, 10);
+    opCanvas.draw(makeDrawRect());
+
+    opCanvas.save();
+    opCanvas.scale(2.0f, 4.0f);
+    opCanvas.draw(makeDrawRect());
+    opCanvas.restore();
+
+    opCanvas.save();
+    opCanvas.translate(20, 15);
+    opCanvas.draw(makeDrawRect());
+    opCanvas.save();
+    opCanvas.rotate(90.f);
+    opCanvas.draw(makeDrawRect());
+    opCanvas.restore();
+    opCanvas.restore();
+
+    // Validate the results
+    std::vector<SkMatrix> transforms;
+    transforms.reserve(5);
+    receiver.for_each([&](auto op) {
+        // Filter for the DrawRect calls; ignore the save & restores
+        // (TODO: Add a filtered for_each variant to OpBuffer?)
+        if (op->type() == CanvasOpType::DrawRect) {
+            transforms.push_back(op->transform());
+        }
+    });
+
+    EXPECT_EQ(transforms.size(), 5);
+
+    {
+        // First result should be identity
+        const auto& result = transforms[0];
+        EXPECT_EQ(SkMatrix::kIdentity_Mask, result.getType());
+        EXPECT_EQ(SkMatrix::I(), result);
+    }
+
+    {
+        // Should be translate 10, 10
+        const auto& result = transforms[1];
+        EXPECT_EQ(SkMatrix::kTranslate_Mask, result.getType());
+        SkMatrix m;
+        m.setTranslate(10, 10);
+        EXPECT_EQ(m, result);
+    }
+
+    {
+        // Should be translate 10, 10 + scale 2, 4
+        const auto& result = transforms[2];
+        EXPECT_EQ(SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask, result.getType());
+        SkMatrix m;
+        m.setTranslate(10, 10);
+        m.preScale(2.0f, 4.0f);
+        EXPECT_EQ(m, result);
+    }
+
+    {
+        // Should be translate 10, 10 + translate 20, 15
+        const auto& result = transforms[3];
+        EXPECT_EQ(SkMatrix::kTranslate_Mask, result.getType());
+        SkMatrix m;
+        m.setTranslate(30, 25);
+        EXPECT_EQ(m, result);
+    }
+
+    {
+        // Should be translate 10, 10 + translate 20, 15 + rotate 90
+        const auto& result = transforms[4];
+        EXPECT_EQ(SkMatrix::kTranslate_Mask | SkMatrix::kAffine_Mask | SkMatrix::kScale_Mask,
+                result.getType());
+        SkMatrix m;
+        m.setTranslate(30, 25);
+        m.preRotate(90.f);
+        EXPECT_EQ(m, result);
+    }
+}
\ No newline at end of file
diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp
index b15c322..f186e55 100644
--- a/libs/hwui/tests/unit/CanvasOpTests.cpp
+++ b/libs/hwui/tests/unit/CanvasOpTests.cpp
@@ -16,6 +16,7 @@
 
 #include <gtest/gtest.h>
 
+#include <canvas/CanvasFrontend.h>
 #include <canvas/CanvasOpBuffer.h>
 #include <canvas/CanvasOps.h>
 #include <canvas/CanvasOpRasterizer.h>
@@ -26,6 +27,7 @@
 #include "SkColor.h"
 #include "SkLatticeIter.h"
 #include "pipeline/skia/AnimatedDrawables.h"
+#include <SkNoDrawCanvas.h>
 
 using namespace android;
 using namespace android::uirenderer;
@@ -78,6 +80,21 @@
 
 using MockBuffer = OpBuffer<MockTypes, MockOpContainer>;
 
+class CanvasOpCountingReceiver {
+public:
+    template <CanvasOpType T>
+    void push_container(CanvasOpContainer<T>&& op) {
+        mOpCounts[static_cast<size_t>(T)] += 1;
+    }
+
+    int operator[](CanvasOpType op) const {
+        return mOpCounts[static_cast<size_t>(op)];
+    }
+
+private:
+    std::array<int, static_cast<size_t>(CanvasOpType::COUNT)> mOpCounts;
+};
+
 template<typename T>
 static int countItems(const T& t) {
     int count = 0;
@@ -614,4 +631,35 @@
     rasterizer.draw(op);
     EXPECT_EQ(1, canvas->drawRectCount);
     EXPECT_EQ(1, canvas->sumTotalDrawCalls());
+}
+
+TEST(CanvasOp, frontendSaveCount) {
+    SkNoDrawCanvas skiaCanvas(100, 100);
+    CanvasFrontend<CanvasOpCountingReceiver> opCanvas(100, 100);
+    const auto& receiver = opCanvas.receiver();
+
+    EXPECT_EQ(1, skiaCanvas.getSaveCount());
+    EXPECT_EQ(1, opCanvas.saveCount());
+
+    skiaCanvas.save();
+    opCanvas.save(SaveFlags::MatrixClip);
+    EXPECT_EQ(2, skiaCanvas.getSaveCount());
+    EXPECT_EQ(2, opCanvas.saveCount());
+
+    skiaCanvas.restore();
+    opCanvas.restore();
+    EXPECT_EQ(1, skiaCanvas.getSaveCount());
+    EXPECT_EQ(1, opCanvas.saveCount());
+
+    skiaCanvas.restore();
+    opCanvas.restore();
+    EXPECT_EQ(1, skiaCanvas.getSaveCount());
+    EXPECT_EQ(1, opCanvas.saveCount());
+
+    EXPECT_EQ(1, receiver[Op::Save]);
+    EXPECT_EQ(1, receiver[Op::Restore]);
+}
+
+TEST(CanvasOp, frontendTransform) {
+
 }
\ No newline at end of file
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
index 5e5a9d9..c33f02df 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
@@ -82,6 +82,7 @@
     private static PendingIntent getPendingActivity(Context context) {
         Intent intent = new Intent("com.android.settings.WIFI_TETHER_SETTINGS")
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+        return PendingIntent.getActivity(context, 0, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
     }
 }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 2a699ea..2e3ea24 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -70,6 +70,7 @@
     <uses-permission android:name="android.permission.SET_PROCESS_LIMIT" />
     <uses-permission android:name="android.permission.SET_ALWAYS_FINISH" />
     <uses-permission android:name="android.permission.DUMP" />
+    <uses-permission android:name="android.permission.CONTROL_UI_TRACING" />
     <uses-permission android:name="android.permission.SIGNAL_PERSISTENT_PROCESSES" />
     <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
     <!-- Internal permissions granted to the shell. -->
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7120cc2..52b41a4 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -530,6 +530,14 @@
             androidprv:alwaysFocusable="true"
             android:excludeFromRecents="true" />
 
+        <!-- started from TvNotificationPanel -->
+        <activity
+            android:name=".statusbar.tv.notifications.TvNotificationPanelActivity"
+            android:excludeFromRecents="true"
+            android:launchMode="singleTask"
+            android:noHistory="true"
+            android:theme="@style/TvSidePanelTheme" />
+
         <!-- started from SliceProvider -->
         <activity android:name=".SlicePermissionActivity"
             android:theme="@style/Theme.SystemUI.Dialog.Alert"
diff --git a/packages/SystemUI/res-keyguard/drawable/circle_white.xml b/packages/SystemUI/res-keyguard/drawable/circle_white.xml
new file mode 100644
index 0000000..d1b2097
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/circle_white.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="oval">
+    <solid android:color="#33FFFFFF" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/disabled_udfps_view.xml b/packages/SystemUI/res/layout/disabled_udfps_view.xml
new file mode 100644
index 0000000..aab8661
--- /dev/null
+++ b/packages/SystemUI/res/layout/disabled_udfps_view.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<com.android.keyguard.DisabledUdfpsView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/disabled_udfps_view"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="@drawable/circle_white"
+/>
diff --git a/packages/SystemUI/res/layout/quick_settings_footer.xml b/packages/SystemUI/res/layout/quick_settings_footer.xml
index e7c7b5f..13572fa 100644
--- a/packages/SystemUI/res/layout/quick_settings_footer.xml
+++ b/packages/SystemUI/res/layout/quick_settings_footer.xml
@@ -26,11 +26,19 @@
     android:layout_gravity="center_vertical|center_horizontal"
     android:background="@android:color/transparent">
 
+    <ImageView
+        android:id="@+id/primary_footer_icon"
+        android:layout_width="@dimen/qs_footer_icon_size"
+        android:layout_height="@dimen/qs_footer_icon_size"
+        android:gravity="start"
+        android:layout_marginEnd="8dp"
+        android:contentDescription="@null"
+        android:tint="?android:attr/textColorPrimary" />
+
     <com.android.systemui.util.AutoMarqueeTextView
         android:id="@+id/footer_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:gravity="start"
         android:layout_weight="1"
         android:singleLine="true"
         android:ellipsize="marquee"
diff --git a/packages/SystemUI/res/layout/quick_settings_footer_dialog_parental_controls.xml b/packages/SystemUI/res/layout/quick_settings_footer_dialog_parental_controls.xml
index 1a35676..3e40321 100644
--- a/packages/SystemUI/res/layout/quick_settings_footer_dialog_parental_controls.xml
+++ b/packages/SystemUI/res/layout/quick_settings_footer_dialog_parental_controls.xml
@@ -29,8 +29,8 @@
         android:orientation="vertical">
         <ImageView
             android:id="@+id/parental_controls_icon"
-            android:layout_width="36dip"
-            android:layout_height="36dip"
+            android:layout_width="24dip"
+            android:layout_height="24dip"
             android:layout_gravity="center_horizontal"
 
         />
diff --git a/packages/SystemUI/res/layout/tv_notification_item.xml b/packages/SystemUI/res/layout/tv_notification_item.xml
new file mode 100644
index 0000000..711cd4e
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_notification_item.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/tv_notification_panel_width"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/selectableItemBackground"
+    android:orientation="vertical"
+    android:padding="12dp">
+
+    <TextView
+        android:id="@+id/tv_notification_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingBottom="12dp"
+        android:textColor="@color/tv_notification_text_color"
+        android:textSize="18sp" />
+
+    <TextView
+        android:id="@+id/tv_notification_details"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textColor="@color/tv_notification_text_color" />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/tv_notification_panel.xml b/packages/SystemUI/res/layout/tv_notification_panel.xml
new file mode 100644
index 0000000..8f00a72
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_notification_panel.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/tv_notification_panel"
+    android:layout_width="@dimen/tv_notification_panel_width"
+    android:layout_height="match_parent"
+    android:layout_gravity="end"
+    android:background="@color/tv_notification_background_color"
+    android:orientation="vertical">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="12dp"
+        android:paddingTop="24dp"
+        android:text="@string/tv_notification_panel_title"
+        android:textColor="@color/tv_notification_text_color"
+        android:textSize="24sp"
+        android:textStyle="bold" />
+
+    <TextView
+        android:id="@+id/no_tv_notifications"
+        style="?android:attr/titleTextStyle"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:gravity="top|center"
+        android:paddingTop="24dp"
+        android:text="@string/tv_notification_panel_no_notifications"
+        android:textColor="@color/tv_notification_text_color"
+        android:visibility="gone" />
+
+    <androidx.leanback.widget.VerticalGridView
+        android:id="@+id/notifications_list"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml
index ccd235d..c078805 100644
--- a/packages/SystemUI/res/layout/udfps_view.xml
+++ b/packages/SystemUI/res/layout/udfps_view.xml
@@ -1,4 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
 <com.android.systemui.biometrics.UdfpsView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index 09ec439..0c8c5c4 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -29,7 +29,8 @@
         <item>com.android.systemui.util.NotificationChannels</item>
         <item>com.android.systemui.volume.VolumeUI</item>
         <item>com.android.systemui.statusbar.tv.TvStatusBar</item>
-        <item>com.android.systemui.statusbar.tv.TvNotificationPanel</item>
+        <item>com.android.systemui.statusbar.tv.notifications.TvNotificationPanel</item>
+        <item>com.android.systemui.statusbar.tv.notifications.TvNotificationHandler</item>
         <item>com.android.systemui.statusbar.tv.VpnStatusObserver</item>
         <item>com.android.systemui.usb.StorageNotification</item>
         <item>com.android.systemui.power.PowerUI</item>
diff --git a/packages/SystemUI/res/values/colors_tv.xml b/packages/SystemUI/res/values/colors_tv.xml
index 9b0ae1d..0961f50 100644
--- a/packages/SystemUI/res/values/colors_tv.xml
+++ b/packages/SystemUI/res/values/colors_tv.xml
@@ -33,4 +33,7 @@
     <color name="tv_volume_dialog_seek_bar_background">#A03C4043</color>
     <color name="tv_volume_dialog_seek_bar_fill">#FFF8F9FA</color>
     <color name="tv_volume_dialog_accent">#FFDADCE0</color>
+
+    <color name="tv_notification_background_color">#383838</color>
+    <color name="tv_notification_text_color">#FFFFFF</color>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens_tv.xml b/packages/SystemUI/res/values/dimens_tv.xml
new file mode 100644
index 0000000..9545bfd
--- /dev/null
+++ b/packages/SystemUI/res/values/dimens_tv.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<resources>
+    <dimen name="tv_notification_panel_width">360dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings_tv.xml b/packages/SystemUI/res/values/strings_tv.xml
index 13271d6..b51cb56 100644
--- a/packages/SystemUI/res/values/strings_tv.xml
+++ b/packages/SystemUI/res/values/strings_tv.xml
@@ -26,4 +26,6 @@
     <!-- Disclosure text in the connected notification that indicates that the device is connected to a VPN. The placeholder is the VPN name. [CHAR LIMIT=40] -->
     <string name="notification_disclosure_vpn_text">Via <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
 
+    <string name="tv_notification_panel_title">Notifications</string>
+    <string name="tv_notification_panel_no_notifications">No Notifications</string>
 </resources>
diff --git a/packages/SystemUI/res/values/styles_tv.xml b/packages/SystemUI/res/values/styles_tv.xml
index 0c4fd23..cb433f3 100644
--- a/packages/SystemUI/res/values/styles_tv.xml
+++ b/packages/SystemUI/res/values/styles_tv.xml
@@ -23,4 +23,12 @@
         <item name="android:backgroundDimEnabled">false</item>
         <item name="android:windowDisablePreview">true</item>
      </style>
+
+    <style name="TvSidePanelTheme">
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:backgroundDimEnabled">false</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java b/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java
new file mode 100644
index 0000000..f01b67b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java
@@ -0,0 +1,155 @@
+/*
+ * 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.keyguard;
+
+import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
+
+import android.hardware.biometrics.BiometricSourceType;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.util.ViewController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Controls when to show the DisabledUdfpsView to unlock the device on the lockscreen.
+ * If the device is not authenticated, the bouncer will show.
+ *
+ * This tap target will only show when:
+ * - User has UDFPS enrolled
+ * - UDFPS is currently unavailable see {@link KeyguardUpdateMonitor#shouldListenForUdfps}
+ */
+@SysUISingleton
+public class DisabledUdfpsController extends ViewController<DisabledUdfpsView> implements Dumpable {
+    @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @NonNull private final KeyguardViewController mKeyguardViewController;
+    @NonNull private final StatusBarStateController mStatusBarStateController;
+
+    private boolean mIsDozing;
+    private boolean mIsBouncerShowing;
+    private boolean mIsKeyguardShowing;
+    private boolean mRunningFPS;
+    private boolean mAuthenticated;
+
+    private boolean mShowButton;
+
+    public DisabledUdfpsController(
+            @NonNull DisabledUdfpsView view,
+            @NonNull StatusBarStateController statusBarStateController,
+            @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
+            @NonNull AuthController authController,
+            @NonNull KeyguardViewController keyguardViewController
+    ) {
+        super(view);
+        mView.setOnClickListener(mOnClickListener);
+        mView.setSensorProperties(authController.getUdfpsProps().get(0));
+
+        mStatusBarStateController = statusBarStateController;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mKeyguardViewController = keyguardViewController;
+    }
+
+    @Override
+    protected void onViewAttached() {
+        mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
+        mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
+
+        mStatusBarStateController.addCallback(mStatusBarStateListener);
+        mIsKeyguardShowing = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+        mIsDozing = mStatusBarStateController.isDozing();
+        mAuthenticated = false;
+    }
+
+    @Override
+    protected void onViewDetached() {
+        mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
+        mStatusBarStateController.removeCallback(mStatusBarStateListener);
+    }
+
+    private void updateButtonVisibility() {
+        mShowButton = !mAuthenticated && !mIsDozing && mIsKeyguardShowing
+                && !mIsBouncerShowing && !mRunningFPS;
+        if (mShowButton) {
+            mView.setVisibility(View.VISIBLE);
+        } else {
+            mView.setVisibility(View.INVISIBLE);
+        }
+    }
+
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("DisabledUdfpsController state:");
+        pw.println("  mShowBouncerButton: " + mShowButton);
+        pw.println("  mIsDozing: " + mIsDozing);
+        pw.println("  mIsKeyguardShowing: " + mIsKeyguardShowing);
+        pw.println("  mIsBouncerShowing: " + mIsBouncerShowing);
+        pw.println("  mRunningFPS: " + mRunningFPS);
+        pw.println("  mAuthenticated: " + mAuthenticated);
+    }
+
+    private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            mKeyguardViewController.showBouncer(/* scrim */ true);
+        }
+    };
+
+    private StatusBarStateController.StateListener mStatusBarStateListener =
+            new StatusBarStateController.StateListener() {
+                @Override
+                public void onStateChanged(int newState) {
+                    mIsKeyguardShowing = newState == StatusBarState.KEYGUARD;
+                    updateButtonVisibility();
+                }
+
+                @Override
+                public void onDozingChanged(boolean isDozing) {
+                    mIsDozing = isDozing;
+                    updateButtonVisibility();
+                }
+            };
+
+    private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
+            new KeyguardUpdateMonitorCallback() {
+                @Override
+                public void onKeyguardBouncerChanged(boolean bouncer) {
+                    mIsBouncerShowing = bouncer;
+                    updateButtonVisibility();
+                }
+
+                @Override
+                public void onBiometricRunningStateChanged(boolean running,
+                        BiometricSourceType biometricSourceType) {
+                    mRunningFPS = running && biometricSourceType == FINGERPRINT;
+                    updateButtonVisibility();
+                }
+
+                @Override
+                public void onUserUnlocked() {
+                    mAuthenticated = true;
+                    updateButtonVisibility();
+                }
+            };
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsView.java b/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsView.java
new file mode 100644
index 0000000..d8ab780
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsView.java
@@ -0,0 +1,89 @@
+/*
+ * 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.keyguard;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.RectF;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.util.AttributeSet;
+import android.view.Surface;
+import android.widget.Button;
+import android.widget.FrameLayout;
+
+/**
+ * A full screen view with an oval target where the UDFPS sensor is.
+ * Controlled by {@link DisabledUdfpsController}.
+ */
+public class DisabledUdfpsView extends Button {
+    @NonNull private final RectF mSensorRect;
+    @NonNull private final Context mContext;
+
+    // Used to obtain the sensor location.
+    @NonNull private FingerprintSensorPropertiesInternal mSensorProps;
+
+    public DisabledUdfpsView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mContext = context;
+        mSensorRect = new RectF();
+    }
+
+    public void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) {
+        mSensorProps = properties;
+    }
+
+    // The "h" and "w" are the display's height and width relative to its current rotation.
+    private void updateSensorRect(int h, int w) {
+        // mSensorProps coordinates assume portrait mode.
+        mSensorRect.set(mSensorProps.sensorLocationX - mSensorProps.sensorRadius,
+                mSensorProps.sensorLocationY - mSensorProps.sensorRadius,
+                mSensorProps.sensorLocationX + mSensorProps.sensorRadius,
+                mSensorProps.sensorLocationY + mSensorProps.sensorRadius);
+
+        // Transform mSensorRect if the device is in landscape mode.
+        switch (mContext.getDisplay().getRotation()) {
+            case Surface.ROTATION_90:
+                mSensorRect.set(mSensorRect.top, h - mSensorRect.right, mSensorRect.bottom,
+                        h - mSensorRect.left);
+                break;
+            case Surface.ROTATION_270:
+                mSensorRect.set(w - mSensorRect.bottom, mSensorRect.left, w - mSensorRect.top,
+                        mSensorRect.right);
+                break;
+            default:
+                // Do nothing to stay in portrait mode.
+        }
+
+        setX(mSensorRect.left);
+        setY(mSensorRect.top);
+        setLayoutParams(new FrameLayout.LayoutParams(
+                (int) (mSensorRect.right - mSensorRect.left),
+                (int) (mSensorRect.bottom - mSensorRect.top)));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        // Always re-compute the layout regardless of whether "changed" is true. It is usually false
+        // when the device goes from landscape to seascape and vice versa, but mSensorRect and
+        // its dependencies need to be recalculated to stay at the same physical location on the
+        // screen.
+        final int w = getLayoutParams().width;
+        final int h = getLayoutParams().height;
+        updateSensorRect(h, w);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 611131f..a7e5195 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1925,6 +1925,7 @@
         }
 
         // TODO: Add support for multiple fingerprint sensors, b/173730729
+        updateUdfpsEnrolled(getCurrentUser());
         boolean shouldListenForFingerprint =
                 isUdfpsEnrolled() ? shouldListenForUdfps() : shouldListenForFingerprint();
         boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING
@@ -2137,7 +2138,6 @@
         }
         if (DEBUG) Log.v(TAG, "startListeningForFingerprint()");
         int userId = getCurrentUser();
-        updateUdfpsEnrolled(userId);
         if (isUnlockWithFingerprintPossible(userId)) {
             if (mFingerprintCancelSignal != null) {
                 mFingerprintCancelSignal.cancel();
@@ -2627,7 +2627,6 @@
         Assert.isMainThread();
         if (DEBUG) Log.d(TAG, "handleKeyguardBouncerChanged(" + bouncerVisible + ")");
         mBouncer = bouncerVisible == 1;
-
         if (mBouncer) {
             // If the bouncer is shown, always clear this flag. This can happen in the following
             // situations: 1) Default camera with SHOW_WHEN_LOCKED is not chosen yet. 2) Secure
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 061d8cf..9edfee7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -410,6 +410,11 @@
         mCurrentDialog.onHelp(message);
     }
 
+    @Nullable
+    public List<FingerprintSensorPropertiesInternal> getUdfpsProps() {
+        return mUdfpsProps;
+    }
+
     private String getErrorString(int modality, int error, int vendorCode) {
         switch (modality) {
             case TYPE_FACE:
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 13ff3f5..ec4a91c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -22,6 +22,7 @@
 import com.android.systemui.keyguard.WorkLockActivity;
 import com.android.systemui.screenrecord.ScreenRecordDialog;
 import com.android.systemui.settings.brightness.BrightnessDialog;
+import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity;
 import com.android.systemui.tuner.TunerActivity;
 import com.android.systemui.usb.UsbDebuggingActivity;
 import com.android.systemui.usb.UsbDebuggingSecondaryUserActivity;
@@ -85,4 +86,10 @@
     @IntoMap
     @ClassKey(CreateUserActivity.class)
     public abstract Activity bindCreateUserActivity(CreateUserActivity activity);
+
+    /** Inject into TvNotificationPanelActivity. */
+    @Binds
+    @IntoMap
+    @ClassKey(TvNotificationPanelActivity.class)
+    public abstract Activity bindTvNotificationPanelActivity(TvNotificationPanelActivity activity);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index c0013d8..9f6c19b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -34,8 +34,8 @@
 import com.android.systemui.statusbar.dagger.StatusBarModule;
 import com.android.systemui.statusbar.notification.InstantAppNotifier;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.tv.TvNotificationPanel;
 import com.android.systemui.statusbar.tv.TvStatusBar;
+import com.android.systemui.statusbar.tv.notifications.TvNotificationPanel;
 import com.android.systemui.theme.ThemeOverlayController;
 import com.android.systemui.toast.ToastUI;
 import com.android.systemui.util.leak.GarbageMonitor;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 4789239..5afe526 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -68,6 +68,7 @@
     private final View mRootView;
     private final TextView mFooterText;
     private final ImageView mFooterIcon;
+    private final ImageView mPrimaryFooterIcon;
     private final Context mContext;
     private final Callback mCallback = new Callback();
     private final SecurityController mSecurityController;
@@ -83,6 +84,7 @@
     private CharSequence mFooterTextContent = null;
     private int mFooterTextId;
     private int mFooterIconId;
+    private Drawable mPrimaryFooterIconDrawable;
 
     @Inject
     QSSecurityFooter(@Named(QS_SECURITY_FOOTER_VIEW) View rootView, Context context,
@@ -92,6 +94,7 @@
         mRootView.setOnClickListener(this);
         mFooterText = mRootView.findViewById(R.id.footer_text);
         mFooterIcon = mRootView.findViewById(R.id.footer_icon);
+        mPrimaryFooterIcon = mRootView.findViewById(R.id.primary_footer_icon);
         mFooterIconId = R.drawable.ic_info_outline;
         mContext = context;
         mMainHandler = mainHandler;
@@ -188,6 +191,18 @@
             mFooterIconId = footerIconId;
             mMainHandler.post(mUpdateIcon);
         }
+
+        // Update the primary icon
+        if (isParentalControlsEnabled) {
+            if (mPrimaryFooterIconDrawable == null) {
+                DeviceAdminInfo info = mSecurityController.getDeviceAdminInfo();
+                mPrimaryFooterIconDrawable = mSecurityController.getIcon(info);
+            }
+        } else {
+            mPrimaryFooterIconDrawable = null;
+        }
+        mMainHandler.post(mUpdatePrimaryIcon);
+
         mMainHandler.post(mUpdateDisplayState);
     }
 
@@ -532,6 +547,15 @@
         }
     };
 
+    private final Runnable mUpdatePrimaryIcon = new Runnable() {
+        @Override
+        public void run() {
+            mPrimaryFooterIcon.setVisibility(mPrimaryFooterIconDrawable != null
+                    ? View.VISIBLE : View.GONE);
+            mPrimaryFooterIcon.setImageDrawable(mPrimaryFooterIconDrawable);
+        }
+    };
+
     private final Runnable mUpdateDisplayState = new Runnable() {
         @Override
         public void run() {
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 f7139aa..bb46172 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -68,6 +68,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.LatencyTracker;
+import com.android.keyguard.DisabledUdfpsController;
 import com.android.keyguard.KeyguardClockSwitchController;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardStatusViewController;
@@ -256,7 +257,25 @@
                     mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
                     mDelayShowingKeyguardStatusBar = false;
                 }
-            };
+
+                @Override
+                public void onKeyguardVisibilityChanged(boolean showing) {
+                    if (mDisabledUdfpsController == null
+                            && mAuthController.getUdfpsRegion() != null
+                            && mAuthController.isUdfpsEnrolled(
+                                   KeyguardUpdateMonitor.getCurrentUser())) {
+                        LayoutInflater.from(mView.getContext())
+                                .inflate(R.layout.disabled_udfps_view, mView);
+                        mDisabledUdfpsController = new DisabledUdfpsController(
+                                mView.findViewById(R.id.disabled_udfps_view),
+                                mStatusBarStateController,
+                                mUpdateMonitor,
+                                mAuthController,
+                                mStatusBarKeyguardViewManager);
+                        mDisabledUdfpsController.init();
+                    }
+                }
+    };
 
     private final InjectionInflationController mInjectionInflationController;
     private final PowerManager mPowerManager;
@@ -284,6 +303,7 @@
     private QS mQs;
     private FrameLayout mQsFrame;
     private KeyguardStatusViewController mKeyguardStatusViewController;
+    private DisabledUdfpsController mDisabledUdfpsController;
     private View mQsNavbarScrim;
     private NotificationsQuickSettingsContainer mNotificationContainerParent;
     private boolean mAnimateNextPositionUpdate;
@@ -3044,6 +3064,9 @@
         if (mKeyguardStatusBar != null) {
             mKeyguardStatusBar.dump(fd, pw, args);
         }
+        if (mDisabledUdfpsController != null) {
+            mDisabledUdfpsController.dump(fd, pw, args);
+        }
     }
 
     public boolean hasActiveClearableNotifications() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index 2795857..bdf2b0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -71,7 +71,6 @@
             // Creating AudioRecordingDisclosureBar and just letting it run
             new AudioRecordingDisclosureBar(mContext);
         }
-
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
new file mode 100644
index 0000000..3b1a4db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
@@ -0,0 +1,111 @@
+/*
+ * 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.systemui.statusbar.tv.notifications;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.systemui.R;
+
+/**
+ * Adapter for the VerticalGridView of the TvNotificationsPanelView.
+ */
+public class TvNotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+    private static final String TAG = "TvNotificationAdapter";
+    private SparseArray<StatusBarNotification> mNotifications;
+
+    public TvNotificationAdapter() {
+        setHasStableIds(true);
+    }
+
+    @NonNull
+    @Override
+    public TvNotificationViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.tv_notification_item,
+                parent, false);
+        return new TvNotificationViewHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
+        if (mNotifications == null) {
+            Log.e(TAG, "Could not bind view holder because the notification is missing");
+            return;
+        }
+
+        TvNotificationViewHolder holder = (TvNotificationViewHolder) viewHolder;
+        Notification notification = mNotifications.valueAt(position).getNotification();
+        holder.mTitle.setText(notification.extras.getString(Notification.EXTRA_TITLE));
+        holder.mDetails.setText(notification.extras.getString(Notification.EXTRA_TEXT));
+        holder.mPendingIntent = notification.contentIntent;
+    }
+
+    @Override
+    public int getItemCount() {
+        return mNotifications == null ? 0 : mNotifications.size();
+    }
+
+    @Override
+    public long getItemId(int position) {
+        // the item id is the notification id
+        return mNotifications.keyAt(position);
+    }
+
+    /**
+     * Updates the notifications and calls notifyDataSetChanged().
+     */
+    public void setNotifications(SparseArray<StatusBarNotification> notifications) {
+        this.mNotifications = notifications;
+        notifyDataSetChanged();
+    }
+
+    private static class TvNotificationViewHolder extends RecyclerView.ViewHolder implements
+            View.OnClickListener {
+        final TextView mTitle;
+        final TextView mDetails;
+        PendingIntent mPendingIntent;
+
+        protected TvNotificationViewHolder(View itemView) {
+            super(itemView);
+            mTitle = itemView.findViewById(R.id.tv_notification_title);
+            mDetails = itemView.findViewById(R.id.tv_notification_details);
+            itemView.setOnClickListener(this);
+        }
+
+        @Override
+        public void onClick(View v) {
+            try {
+                if (mPendingIntent != null) {
+                    mPendingIntent.send();
+                }
+            } catch (PendingIntent.CanceledException e) {
+                Log.d(TAG, "Pending intent canceled for : " + mPendingIntent);
+            }
+        }
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java
new file mode 100644
index 0000000..d985803
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java
@@ -0,0 +1,115 @@
+/*
+ * 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.systemui.statusbar.tv.notifications;
+
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.Context;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.systemui.SystemUI;
+import com.android.systemui.statusbar.NotificationListener;
+
+import javax.inject.Inject;
+
+/**
+ * Keeps track of the notifications on TV.
+ */
+public class TvNotificationHandler extends SystemUI implements
+        NotificationListener.NotificationHandler {
+    private static final String TAG = "TvNotificationHandler";
+    private final NotificationListener mNotificationListener;
+    private final SparseArray<StatusBarNotification> mNotifications = new SparseArray<>();
+    @Nullable
+    private Listener mUpdateListener;
+
+    @Inject
+    public TvNotificationHandler(Context context, NotificationListener notificationListener) {
+        super(context);
+        mNotificationListener = notificationListener;
+    }
+
+    public SparseArray<StatusBarNotification> getCurrentNotifications() {
+        return mNotifications;
+    }
+
+    public void setTvNotificationListener(Listener listener) {
+        mUpdateListener = listener;
+    }
+
+    @Override
+    public void start() {
+        mNotificationListener.addNotificationHandler(this);
+        mNotificationListener.registerAsSystemService();
+    }
+
+    @Override
+    public void onNotificationPosted(StatusBarNotification sbn,
+            NotificationListenerService.RankingMap rankingMap) {
+        if (!new Notification.TvExtender(sbn.getNotification()).isAvailableOnTv()) {
+            Log.v(TAG, "Notification not added because it isn't relevant for tv");
+            return;
+        }
+
+        mNotifications.put(sbn.getId(), sbn);
+        if (mUpdateListener != null) {
+            mUpdateListener.notificationsUpdated(mNotifications);
+        }
+        Log.d(TAG, "Notification added");
+    }
+
+    @Override
+    public void onNotificationRemoved(StatusBarNotification sbn,
+            NotificationListenerService.RankingMap rankingMap) {
+
+        if (mNotifications.contains(sbn.getId())) {
+            mNotifications.remove(sbn.getId());
+            Log.d(TAG, "Notification removed");
+
+            if (mUpdateListener != null) {
+                mUpdateListener.notificationsUpdated(mNotifications);
+            }
+        }
+    }
+
+    @Override
+    public void onNotificationRemoved(StatusBarNotification sbn,
+            NotificationListenerService.RankingMap rankingMap, int reason) {
+        onNotificationRemoved(sbn, rankingMap);
+    }
+
+    @Override
+    public void onNotificationRankingUpdate(NotificationListenerService.RankingMap rankingMap) {
+        // noop
+    }
+
+    @Override
+    public void onNotificationsInitialized() {
+        // noop
+    }
+
+    /**
+     * Get notified when the notifications are updated.
+     */
+    interface Listener {
+        void notificationsUpdated(SparseArray<StatusBarNotification> sbns);
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java
index 0bd3624..477424c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.tv;
+package com.android.systemui.statusbar.tv.notifications;
 
 import android.Manifest;
 import android.app.NotificationManager;
@@ -59,9 +59,8 @@
             startNotificationHandlerActivity(
                     new Intent(NotificationManager.ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL));
         } else {
-            Log.w(TAG,
-                    "Not toggling notification panel: config_notificationHandlerPackage is "
-                            + "empty");
+            openInternalNotificationPanel(
+                    NotificationManager.ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL);
         }
     }
 
@@ -71,9 +70,8 @@
             startNotificationHandlerActivity(
                     new Intent(NotificationManager.ACTION_OPEN_NOTIFICATION_HANDLER_PANEL));
         } else {
-            Log.w(TAG,
-                    "Not expanding notification panel: config_notificationHandlerPackage is "
-                            + "empty");
+            openInternalNotificationPanel(
+                    NotificationManager.ACTION_OPEN_NOTIFICATION_HANDLER_PANEL);
         }
     }
 
@@ -86,11 +84,17 @@
             closeNotificationIntent.setPackage(mNotificationHandlerPackage);
             mContext.sendBroadcastAsUser(closeNotificationIntent, UserHandle.CURRENT);
         } else {
-            Log.w(TAG,
-                    "Not closing notification panel: config_notificationHandlerPackage is empty");
+            openInternalNotificationPanel(
+                    NotificationManager.ACTION_CLOSE_NOTIFICATION_HANDLER_PANEL);
         }
     }
 
+    private void openInternalNotificationPanel(String action) {
+        Intent intent = new Intent(mContext, TvNotificationPanelActivity.class);
+        intent.setAction(action);
+        mContext.startActivityAsUser(intent, UserHandle.SYSTEM);
+    }
+
     /**
      * Starts the activity intent if all of the following are true
      * <ul>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanelActivity.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanelActivity.java
new file mode 100644
index 0000000..30f401b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanelActivity.java
@@ -0,0 +1,110 @@
+/*
+ * 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.systemui.statusbar.tv.notifications;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.service.notification.StatusBarNotification;
+import android.util.SparseArray;
+import android.view.View;
+
+import androidx.leanback.widget.VerticalGridView;
+
+import com.android.systemui.R;
+
+import javax.inject.Inject;
+
+/**
+ * This Activity shows a notification panel for tv. It is used if no other app (e.g. a launcher) can
+ * be found to show the notifications.
+ */
+public class TvNotificationPanelActivity extends Activity implements
+        TvNotificationHandler.Listener {
+    private final TvNotificationHandler mTvNotificationHandler;
+    private TvNotificationAdapter mTvNotificationAdapter;
+    private VerticalGridView mNotificationListView;
+    private View mNotificationPlaceholder;
+    private boolean mPanelAlreadyOpen = false;
+
+    @Inject
+    public TvNotificationPanelActivity(TvNotificationHandler tvNotificationHandler) {
+        super();
+        mTvNotificationHandler = tvNotificationHandler;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (maybeClosePanel(getIntent())) {
+            return;
+        }
+        mPanelAlreadyOpen = true;
+
+        setContentView(R.layout.tv_notification_panel);
+
+        mNotificationPlaceholder = findViewById(R.id.no_tv_notifications);
+        mTvNotificationAdapter = new TvNotificationAdapter();
+
+        mNotificationListView = findViewById(R.id.notifications_list);
+        mNotificationListView.setAdapter(mTvNotificationAdapter);
+        mNotificationListView.setColumnWidth(R.dimen.tv_notification_panel_width);
+
+        mTvNotificationHandler.setTvNotificationListener(this);
+        notificationsUpdated(mTvNotificationHandler.getCurrentNotifications());
+    }
+
+    @Override
+    public void notificationsUpdated(@NonNull SparseArray<StatusBarNotification> notificationList) {
+        mTvNotificationAdapter.setNotifications(notificationList);
+
+        boolean noNotifications = notificationList.size() == 0;
+        mNotificationListView.setVisibility(noNotifications ? View.GONE : View.VISIBLE);
+        mNotificationPlaceholder.setVisibility(noNotifications ? View.VISIBLE : View.GONE);
+    }
+
+    @Override
+    public void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        maybeClosePanel(intent);
+    }
+
+    /**
+     * Handles intents from onCreate and onNewIntent.
+     *
+     * @return true if the panel is being closed, false if it is being opened
+     */
+    private boolean maybeClosePanel(Intent intent) {
+        if (NotificationManager.ACTION_CLOSE_NOTIFICATION_HANDLER_PANEL.equals(intent.getAction())
+                || (mPanelAlreadyOpen
+                && NotificationManager.ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL.equals(
+                intent.getAction()))) {
+            finish();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mTvNotificationHandler.setTvNotificationListener(null);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
index 2c3ea4f..353333f 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
@@ -16,14 +16,22 @@
 
 package com.android.systemui.tv;
 
+import com.android.systemui.SystemUI;
 import com.android.systemui.dagger.GlobalRootComponent;
-import com.android.systemui.wmshell.TvPipModule;
+import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler;
 
 import dagger.Binds;
 import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
 
 @Module
 interface TvSystemUIBinder {
     @Binds
     GlobalRootComponent bindGlobalRootComponent(TvGlobalRootComponent globalRootComponent);
+
+    @Binds
+    @IntoMap
+    @ClassKey(TvNotificationHandler.class)
+    SystemUI bindTvNotificationHandler(TvNotificationHandler systemui);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 8ffc7cf..56a4c203 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -43,6 +43,7 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImplementation;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -62,6 +63,7 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler;
 
 import javax.inject.Named;
 
@@ -164,4 +166,11 @@
 
     @Binds
     abstract DozeHost provideDozeHost(DozeServiceHost dozeServiceHost);
+
+    @Provides
+    @SysUISingleton
+    static TvNotificationHandler provideTvNotificationHandler(Context context,
+            NotificationListener notificationListener) {
+        return new TvNotificationHandler(context, notificationListener);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 6fa6f31..fd0715b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -15,6 +15,7 @@
 package com.android.systemui.qs;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
 
 import static org.junit.Assert.assertFalse;
 import static org.mockito.Matchers.any;
@@ -24,6 +25,8 @@
 import static org.mockito.Mockito.when;
 
 import android.content.pm.UserInfo;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.VectorDrawable;
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.Settings;
@@ -75,6 +78,7 @@
     private ViewGroup mRootView;
     private TextView mFooterText;
     private TestableImageView mFooterIcon;
+    private TestableImageView mPrimaryFooterIcon;
     private QSSecurityFooter mFooter;
     @Mock
     private SecurityController mSecurityController;
@@ -95,6 +99,7 @@
                 mActivityStarter, mSecurityController, looper);
         mFooterText = mRootView.findViewById(R.id.footer_text);
         mFooterIcon = mRootView.findViewById(R.id.footer_icon);
+        mPrimaryFooterIcon = mRootView.findViewById(R.id.primary_footer_icon);
         mFooter.setHostEnvironment(null);
     }
 
@@ -119,6 +124,7 @@
                      mFooterText.getText());
         assertEquals(View.VISIBLE, mRootView.getVisibility());
         assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+        assertEquals(View.GONE, mPrimaryFooterIcon.getVisibility());
         // -1 == never set.
         assertEquals(-1, mFooterIcon.getLastImageResource());
     }
@@ -136,6 +142,7 @@
                 mFooterText.getText());
         assertEquals(View.VISIBLE, mRootView.getVisibility());
         assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+        assertEquals(View.GONE, mPrimaryFooterIcon.getVisibility());
         // -1 == never set.
         assertEquals(-1, mFooterIcon.getLastImageResource());
     }
@@ -165,6 +172,7 @@
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring),
                 mFooterText.getText());
         assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+        assertEquals(View.GONE, mPrimaryFooterIcon.getVisibility());
         // -1 == never set.
         assertEquals(-1, mFooterIcon.getLastImageResource());
 
@@ -203,6 +211,7 @@
                                         VPN_PACKAGE),
                      mFooterText.getText());
         assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+        assertEquals(View.GONE, mPrimaryFooterIcon.getVisibility());
         assertEquals(R.drawable.stat_sys_vpn_ic, mFooterIcon.getLastImageResource());
 
         // Same situation, but with organization name set
@@ -229,6 +238,7 @@
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_vpns),
                      mFooterText.getText());
         assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+        assertEquals(View.GONE, mPrimaryFooterIcon.getVisibility());
         assertEquals(R.drawable.stat_sys_vpn_ic, mFooterIcon.getLastImageResource());
 
         // Same situation, but with organization name set
@@ -252,6 +262,7 @@
 
         TestableLooper.get(this).processAllMessages();
         assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+        assertEquals(View.GONE, mPrimaryFooterIcon.getVisibility());
         assertEquals(R.drawable.stat_sys_vpn_ic, mFooterIcon.getLastImageResource());
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring),
                 mFooterText.getText());
@@ -534,12 +545,27 @@
     @Test
     public void testParentalControls() {
         when(mSecurityController.isParentalControlsEnabled()).thenReturn(true);
+
+        Drawable testDrawable = new VectorDrawable();
+        when(mSecurityController.getIcon(any())).thenReturn(testDrawable);
+        assertNotNull(mSecurityController.getIcon(null));
+
         mFooter.refreshState();
 
         TestableLooper.get(this).processAllMessages();
 
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_parental_controls),
                 mFooterText.getText());
+        assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
+
+        assertEquals(testDrawable, mPrimaryFooterIcon.getDrawable());
+
+        // Ensure the primary icon is set to gone when parental controls is disabled.
+        when(mSecurityController.isParentalControlsEnabled()).thenReturn(false);
+        mFooter.refreshState();
+        TestableLooper.get(this).processAllMessages();
+
+        assertEquals(View.GONE, mPrimaryFooterIcon.getVisibility());
     }
 
     @Test
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 8c2d309..dbd27af4 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1312,12 +1312,13 @@
                     final int oldState = vol.state;
                     final int newState = state;
                     vol.state = newState;
+                    final VolumeInfo vInfo = new VolumeInfo(vol);
                     final SomeArgs args = SomeArgs.obtain();
-                    args.arg1 = vol;
+                    args.arg1 = vInfo;
                     args.arg2 = oldState;
                     args.arg3 = newState;
                     mHandler.obtainMessage(H_VOLUME_STATE_CHANGED, args).sendToTarget();
-                    onVolumeStateChangedLocked(vol, oldState, newState);
+                    onVolumeStateChangedLocked(vInfo, oldState, newState);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 5189945..a257cde 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -158,6 +158,8 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.content.PackageMonitor;
+import com.android.internal.inputmethod.CallbackUtils;
+import com.android.internal.inputmethod.IInputBindResultResultCallback;
 import com.android.internal.inputmethod.IInputContentUriToken;
 import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
 import com.android.internal.inputmethod.InputMethodDebug;
@@ -208,6 +210,7 @@
 import java.util.WeakHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
 
 /**
  * This class provides a system service that manages input methods.
@@ -3347,63 +3350,68 @@
 
     @NonNull
     @Override
-    public InputBindResult startInputOrWindowGainedFocus(
+    public void startInputOrWindowGainedFocus(
             @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
             @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
             int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext,
-            @MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion) {
-        if (windowToken == null) {
-            Slog.e(TAG, "windowToken cannot be null.");
-            return InputBindResult.NULL;
-        }
-        try {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
-                    "IMMS.startInputOrWindowGainedFocus");
-            ImeTracing.getInstance().triggerManagerServiceDump(
-                    "InputMethodManagerService#startInputOrWindowGainedFocus");
-            final int callingUserId = UserHandle.getCallingUserId();
-            final int userId;
-            if (attribute != null && attribute.targetInputMethodUser != null
-                    && attribute.targetInputMethodUser.getIdentifier() != callingUserId) {
-                mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                        "Using EditorInfo.targetInputMethodUser requires"
-                                + " INTERACT_ACROSS_USERS_FULL.");
-                userId = attribute.targetInputMethodUser.getIdentifier();
-                if (!mUserManagerInternal.isUserRunning(userId)) {
-                    // There is a chance that we hit here because of race condition.  Let's just
-                    // return an error code instead of crashing the caller process, which at least
-                    // has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an important
-                    // process.
-                    Slog.e(TAG, "User #" + userId + " is not running.");
-                    return InputBindResult.INVALID_USER;
-                }
-            } else {
-                userId = callingUserId;
-            }
-            final InputBindResult result;
-            synchronized (mMethodMap) {
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    result = startInputOrWindowGainedFocusInternalLocked(startInputReason, client,
-                            windowToken, startInputFlags, softInputMode, windowFlags, attribute,
-                            inputContext, missingMethods, unverifiedTargetSdkVersion, userId);
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
-            }
-            if (result == null) {
-                // This must never happen, but just in case.
-                Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
-                        + InputMethodDebug.startInputReasonToString(startInputReason)
-                        + " windowFlags=#" + Integer.toHexString(windowFlags)
-                        + " editorInfo=" + attribute);
+            @MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion,
+            IInputBindResultResultCallback resultCallback) {
+        CallbackUtils.onResult(resultCallback, (Supplier<InputBindResult>) () -> {
+            if (windowToken == null) {
+                Slog.e(TAG, "windowToken cannot be null.");
                 return InputBindResult.NULL;
             }
+            try {
+                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+                        "IMMS.startInputOrWindowGainedFocus");
+                ImeTracing.getInstance().triggerManagerServiceDump(
+                        "InputMethodManagerService#startInputOrWindowGainedFocus");
+                final int callingUserId = UserHandle.getCallingUserId();
+                final int userId;
+                if (attribute != null && attribute.targetInputMethodUser != null
+                        && attribute.targetInputMethodUser.getIdentifier() != callingUserId) {
+                    mContext.enforceCallingPermission(
+                            Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                            "Using EditorInfo.targetInputMethodUser requires"
+                                    + " INTERACT_ACROSS_USERS_FULL.");
+                    userId = attribute.targetInputMethodUser.getIdentifier();
+                    if (!mUserManagerInternal.isUserRunning(userId)) {
+                        // There is a chance that we hit here because of race condition. Let's just
+                        // return an error code instead of crashing the caller process, which at
+                        // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
+                        // important process.
+                        Slog.e(TAG, "User #" + userId + " is not running.");
+                        return InputBindResult.INVALID_USER;
+                    }
+                } else {
+                    userId = callingUserId;
+                }
+                final InputBindResult result;
+                synchronized (mMethodMap) {
+                    final long ident = Binder.clearCallingIdentity();
+                    try {
+                        result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
+                                client, windowToken, startInputFlags, softInputMode, windowFlags,
+                                attribute, inputContext, missingMethods, unverifiedTargetSdkVersion,
+                                userId);
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
+                    }
+                }
+                if (result == null) {
+                    // This must never happen, but just in case.
+                    Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
+                            + InputMethodDebug.startInputReasonToString(startInputReason)
+                            + " windowFlags=#" + Integer.toHexString(windowFlags)
+                            + " editorInfo=" + attribute);
+                    return InputBindResult.NULL;
+                }
 
-            return result;
-        } finally {
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-        }
+                return result;
+            } finally {
+                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+            }
+        });
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 62d817c..6bdae63 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -72,6 +72,8 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.inputmethod.CallbackUtils;
+import com.android.internal.inputmethod.IInputBindResultResultCallback;
 import com.android.internal.inputmethod.IMultiClientInputMethod;
 import com.android.internal.inputmethod.IMultiClientInputMethodPrivilegedOperations;
 import com.android.internal.inputmethod.IMultiClientInputMethodSession;
@@ -104,6 +106,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.WeakHashMap;
+import java.util.function.Supplier;
 
 /**
  * Actual implementation of multi-client InputMethodManagerService.
@@ -1588,7 +1591,26 @@
 
         @BinderThread
         @Override
-        public InputBindResult startInputOrWindowGainedFocus(
+        public void startInputOrWindowGainedFocus(
+                @StartInputReason int startInputReason,
+                @Nullable IInputMethodClient client,
+                @Nullable IBinder windowToken,
+                @StartInputFlags int startInputFlags,
+                @SoftInputModeFlags int softInputMode,
+                int windowFlags,
+                @Nullable EditorInfo editorInfo,
+                @Nullable IInputContext inputContext,
+                @MissingMethodFlags int missingMethods,
+                int unverifiedTargetSdkVersion,
+                IInputBindResultResultCallback resultCallback) {
+            CallbackUtils.onResult(resultCallback, (Supplier<InputBindResult>) () ->
+                    startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken,
+                            startInputFlags, softInputMode, windowFlags, editorInfo, inputContext,
+                            missingMethods, unverifiedTargetSdkVersion));
+        }
+
+        @BinderThread
+        private InputBindResult startInputOrWindowGainedFocusInternal(
                 @StartInputReason int startInputReason,
                 @Nullable IInputMethodClient client,
                 @Nullable IBinder windowToken,
@@ -1676,8 +1698,7 @@
                                 clientInfo.mMSInputMethodSession.startInputOrWindowGainedFocus(
                                         inputContext, missingMethods, editorInfo, startInputFlags,
                                         softInputMode, windowHandle);
-                            } catch (RemoteException e) {
-                            }
+                            } catch (RemoteException ignored) { }
                             break;
                     }
                     return InputBindResult.NULL_EDITOR_INFO;
@@ -1708,8 +1729,7 @@
                             clientInfo.mMSInputMethodSession.startInputOrWindowGainedFocus(
                                     inputContext, missingMethods, editorInfo, startInputFlags,
                                     softInputMode, windowHandle);
-                        } catch (RemoteException e) {
-                        }
+                        } catch (RemoteException ignored) { }
                         clientInfo.mState = InputMethodClientState.ALREADY_SENT_BIND_RESULT;
                         return new InputBindResult(
                                 InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6379080..5df5050 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1679,6 +1679,28 @@
         }
     }
 
+    void notifyInsetsChanged(Consumer<WindowState> dispatchInsetsChanged) {
+        if (mFixedRotationLaunchingApp != null) {
+            // The insets state of fixed rotation app is a rotated copy. Make sure the visibilities
+            // of insets sources are consistent with the latest state.
+            final InsetsState rotatedState =
+                    mFixedRotationLaunchingApp.getFixedRotationTransformInsetsState();
+            if (rotatedState != null) {
+                final InsetsState state = mInsetsStateController.getRawInsetsState();
+                for (int i = 0; i < InsetsState.SIZE; i++) {
+                    final InsetsSource source = state.peekSource(i);
+                    if (source != null) {
+                        rotatedState.setSourceVisible(i, source.isVisible());
+                    }
+                }
+            }
+        }
+        forAllWindows(dispatchInsetsChanged, true /* traverseTopToBottom */);
+        if (mRemoteInsetsControlTarget != null) {
+            mRemoteInsetsControlTarget.notifyInsetsChanged();
+        }
+    }
+
     /**
      * Update rotation of the display.
      *
@@ -5702,4 +5724,8 @@
         }
         return count;
     }
+
+    MagnificationSpec getMagnificationSpec() {
+        return mMagnificationSpec;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ImpressionAttestationController.java b/services/core/java/com/android/server/wm/ImpressionAttestationController.java
index 4793e1b..b0afc57 100644
--- a/services/core/java/com/android/server/wm/ImpressionAttestationController.java
+++ b/services/core/java/com/android/server/wm/ImpressionAttestationController.java
@@ -18,6 +18,9 @@
 
 import static android.service.attestation.ImpressionAttestationService.SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS;
 
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -29,7 +32,9 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
+import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.hardware.HardwareBuffer;
 import android.os.Binder;
 import android.os.Bundle;
@@ -43,6 +48,7 @@
 import android.service.attestation.ImpressionAttestationService;
 import android.service.attestation.ImpressionToken;
 import android.util.Slog;
+import android.view.MagnificationSpec;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -59,7 +65,8 @@
  * blocking calls into another service.
  */
 public class ImpressionAttestationController {
-    private static final String TAG = "ImpressionAttestationController";
+    private static final String TAG =
+            TAG_WITH_CLASS_NAME ? "ImpressionAttestationController" : TAG_WM;
     private static final boolean DEBUG = false;
 
     private final Object mServiceConnectionLock = new Object();
@@ -81,6 +88,10 @@
 
     private final String mSalt;
 
+    private final float[] mTmpFloat9 = new float[9];
+    private final Matrix mTmpMatrix = new Matrix();
+    private final RectF mTmpRectF = new RectF();
+
     private interface Command {
         void run(IImpressionAttestationService service) throws RemoteException;
     }
@@ -151,6 +162,79 @@
     }
 
     /**
+     * Calculate the bounds to take the screenshot when generating the impression token. This takes
+     * into account window transform, magnification, and display bounds.
+     *
+     * Call while holding {@link WindowManagerService#mGlobalLock}
+     *
+     * @param win Window that the impression token is generated for.
+     * @param boundsInWindow The bounds passed in about where in the window to take the screenshot.
+     * @param outBounds The result of the calculated bounds
+     */
+    void calculateImpressionTokenBoundsLocked(WindowState win, Rect boundsInWindow,
+            Rect outBounds) {
+        if (DEBUG) {
+            Slog.d(TAG, "calculateImpressionTokenBounds: boundsInWindow=" + boundsInWindow);
+        }
+        outBounds.set(boundsInWindow);
+
+        DisplayContent displayContent = win.getDisplayContent();
+        if (displayContent == null) {
+            return;
+        }
+
+        // Intersect boundsInWindow with the window to make sure it's not outside the window
+        // requesting the token. Offset the window bounds to 0,0 since the boundsInWindow are
+        // offset from the window location, not display.
+        final Rect windowBounds = new Rect();
+        win.getBounds(windowBounds);
+        windowBounds.offsetTo(0, 0);
+        outBounds.intersectUnchecked(windowBounds);
+
+        if (DEBUG) {
+            Slog.d(TAG, "calculateImpressionTokenBounds: boundsIntersectWindow=" + outBounds);
+        }
+
+        if (outBounds.isEmpty()) {
+            return;
+        }
+
+        // Transform the bounds using the window transform in case there's a scale or offset.
+        // This allows the bounds to be in display space.
+        win.getTransformationMatrix(mTmpFloat9, mTmpMatrix);
+        mTmpRectF.set(outBounds);
+        mTmpMatrix.mapRect(mTmpRectF, mTmpRectF);
+        outBounds.set((int) mTmpRectF.left, (int) mTmpRectF.top, (int) mTmpRectF.right,
+                (int) mTmpRectF.bottom);
+        if (DEBUG) {
+            Slog.d(TAG, "calculateImpressionTokenBounds: boundsInDisplay=" + outBounds);
+        }
+
+        // Apply the magnification spec values to the bounds since the content could be magnified
+        final MagnificationSpec magSpec = displayContent.getMagnificationSpec();
+        if (magSpec != null) {
+            outBounds.scale(magSpec.scale);
+            outBounds.offset((int) magSpec.offsetX, (int) magSpec.offsetY);
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG, "calculateImpressionTokenBounds: boundsWithMagnification=" + outBounds);
+        }
+
+        if (outBounds.isEmpty()) {
+            return;
+        }
+
+        // Intersect with the display bounds since it shouldn't take a screenshot of content
+        // outside the display since it's not visible to the user.
+        final Rect displayBounds = displayContent.getBounds();
+        outBounds.intersectUnchecked(displayBounds);
+        if (DEBUG) {
+            Slog.d(TAG, "calculateImpressionTokenBounds: finalBounds=" + outBounds);
+        }
+    }
+
+    /**
      * Run a command, starting the service connection if necessary.
      */
     private void connectAndRun(@NonNull Command command) {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index b49d83d..25d779f 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -40,6 +40,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
@@ -655,6 +656,7 @@
                 || type == TYPE_SECURE_SYSTEM_OVERLAY
                 || type == TYPE_DOCK_DIVIDER
                 || type == TYPE_ACCESSIBILITY_OVERLAY
-                || type == TYPE_INPUT_CONSUMER;
+                || type == TYPE_INPUT_CONSUMER
+                || type == TYPE_VOICE_INTERACTION;
     }
 }
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 9d70fd7..752d6b4 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -496,10 +496,7 @@
     }
 
     void notifyInsetsChanged() {
-        mDisplayContent.forAllWindows(mDispatchInsetsChanged, true /* traverseTopToBottom */);
-        if (mDisplayContent.mRemoteInsetsControlTarget != null) {
-            mDisplayContent.mRemoteInsetsControlTarget.notifyInsetsChanged();
-        }
+        mDisplayContent.notifyInsetsChanged(mDispatchInsetsChanged);
     }
 
     void dump(String prefix, PrintWriter pw) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index b46e796..c414c64 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -38,7 +38,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.Nullable;
-import android.app.ActivityManagerInternal;
 import android.app.PendingIntent;
 import android.content.ClipData;
 import android.content.ClipDescription;
@@ -56,6 +55,7 @@
 import android.os.RemoteException;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.service.attestation.ImpressionToken;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.MergedConfiguration;
@@ -848,4 +848,15 @@
             Binder.restoreCallingIdentity(identity);
         }
     }
+
+    @Override
+    public ImpressionToken generateImpressionToken(IWindow window, Rect boundsInWindow,
+            String hashAlgorithm) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            return mService.generateImpressionToken(this, window, boundsInWindow, hashAlgorithm);
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c585f22..25049ae 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -199,6 +199,7 @@
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.provider.Settings;
+import android.service.attestation.ImpressionToken;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
 import android.sysprop.SurfaceFlingerProperties;
@@ -767,6 +768,8 @@
     final EmbeddedWindowController mEmbeddedWindowController;
     final AnrController mAnrController;
 
+    private final ImpressionAttestationController mImpressionAttestationController;
+
     @VisibleForTesting
     final class SettingsObserver extends ContentObserver {
         private final Uri mDisplayInversionEnabledUri =
@@ -1367,6 +1370,7 @@
         mDisplayAreaPolicyProvider = DisplayAreaPolicy.Provider.fromResources(
                 mContext.getResources());
 
+        mImpressionAttestationController = new ImpressionAttestationController(mContext);
         setGlobalShadowSettings();
         mAnrController = new AnrController(this);
         mStartingSurfaceController = new StartingSurfaceController(this);
@@ -8428,4 +8432,64 @@
             SystemClock.sleep(durationMs);
         }
     }
+
+    @Override
+    public String[] getSupportedImpressionAlgorithms() {
+        return mImpressionAttestationController.getSupportedImpressionAlgorithms();
+    }
+
+    @Override
+    public boolean verifyImpressionToken(ImpressionToken impressionToken) {
+        return mImpressionAttestationController.verifyImpressionToken(impressionToken);
+    }
+
+    ImpressionToken generateImpressionToken(Session session, IWindow window,
+            Rect boundsInWindow, String hashAlgorithm) {
+        final SurfaceControl displaySurfaceControl;
+        final Rect boundsInDisplay = new Rect(boundsInWindow);
+        synchronized (mGlobalLock) {
+            final WindowState win = windowForClientLocked(session, window, false);
+            if (win == null) {
+                Slog.w(TAG, "Failed to generate impression token. Invalid window");
+                return null;
+            }
+
+            DisplayContent displayContent = win.getDisplayContent();
+            if (displayContent == null) {
+                Slog.w(TAG, "Failed to generate impression token. Window is not on a display");
+                return null;
+            }
+
+            displaySurfaceControl = displayContent.getSurfaceControl();
+            mImpressionAttestationController.calculateImpressionTokenBoundsLocked(win,
+                    boundsInWindow, boundsInDisplay);
+
+            if (boundsInDisplay.isEmpty()) {
+                Slog.w(TAG, "Failed to generate impression token. Bounds are not on screen");
+                return null;
+            }
+        }
+
+        // A screenshot of the entire display is taken rather than just the window. This is
+        // because if we take a screenshot of the window, it will not include content that might
+        // be covering it with the same uid. We want to make sure we include content that's
+        // covering to ensure we get as close as possible to what the user sees
+        final int uid = session.mUid;
+        SurfaceControl.LayerCaptureArgs args =
+                new SurfaceControl.LayerCaptureArgs.Builder(displaySurfaceControl)
+                        .setUid(uid)
+                        .setSourceCrop(boundsInDisplay)
+                        .build();
+
+        SurfaceControl.ScreenshotHardwareBuffer screenshotHardwareBuffer =
+                SurfaceControl.captureLayers(args);
+        if (screenshotHardwareBuffer == null
+                || screenshotHardwareBuffer.getHardwareBuffer() == null) {
+            Slog.w(TAG, "Failed to generate impression token. Failed to take screenshot");
+            return null;
+        }
+
+        return mImpressionAttestationController.generateImpressionToken(
+                screenshotHardwareBuffer.getHardwareBuffer(), boundsInWindow, hashAlgorithm);
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index ce61d50..05b1e425 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -101,4 +101,9 @@
     public boolean canProfileOwnerResetPasswordWhenLocked(int userId) {
         return false;
     }
+
+    public boolean hasKeyPair(String callerPackage, String alias) {
+        // STOPSHIP: implement delegation code in ArcDevicePolicyManagerWrapperService & nuke this.
+        return false;
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f4bd0112..83b4c82 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5111,6 +5111,30 @@
     }
 
     @Override
+    public boolean hasKeyPair(String callerPackage, String alias) {
+        final CallerIdentity caller = getCallerIdentity(callerPackage);
+        Preconditions.checkCallAuthorization(canManageCertificates(caller));
+
+        return mInjector.binderWithCleanCallingIdentity(() -> {
+            try (KeyChainConnection keyChainConnection =
+                         KeyChain.bindAsUser(mContext, caller.getUserHandle())) {
+                return keyChainConnection.getService().containsKeyPair(alias);
+            } catch (RemoteException e) {
+                Log.e(LOG_TAG, "Querying keypair", e);
+            } catch (InterruptedException e) {
+                Log.w(LOG_TAG, "Interrupted while querying keypair", e);
+                Thread.currentThread().interrupt();
+            }
+            return false;
+        });
+    }
+
+    private boolean canManageCertificates(CallerIdentity caller) {
+        return isProfileOwner(caller) || isDeviceOwner(caller)
+                || isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
+    }
+
+    @Override
     public boolean setKeyGrantForApp(ComponentName who, String callerPackage, String alias,
             String packageName, boolean hasGrant) {
         Preconditions.checkStringNotEmpty(alias, "Alias to grant cannot be empty");
@@ -5188,9 +5212,7 @@
          */
         if (hasProfileOwner(caller.getUserId())) {
             // Make sure that the caller is the profile owner or delegate.
-            Preconditions.checkCallAuthorization(
-                    isDeviceOwner(caller) || isProfileOwner(caller) || isCallerDelegate(
-                            caller, DELEGATION_CERT_INSTALL));
+            Preconditions.checkCallAuthorization(canManageCertificates(caller));
             // Verify that the managed profile is on an organization-owned device and as such
             // the profile owner can access Device IDs.
             if (isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId())) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d921718..64065e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1202,6 +1202,17 @@
         assertTrue(mNavBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
                 ANIMATION_TYPE_FIXED_TRANSFORM));
 
+        // If the visibility of insets state is changed, the rotated state should be updated too.
+        final InsetsState rotatedState = app.getFixedRotationTransformInsetsState();
+        final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
+        assertEquals(state.getSource(ITYPE_STATUS_BAR).isVisible(),
+                rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
+        state.getSource(ITYPE_STATUS_BAR).setVisible(
+                !rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
+        mDisplayContent.getInsetsStateController().notifyInsetsChanged();
+        assertEquals(state.getSource(ITYPE_STATUS_BAR).isVisible(),
+                rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
+
         final Rect outFrame = new Rect();
         final Rect outInsets = new Rect();
         final Rect outStableInsets = new Rect();
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index d12a6ae..5848be8 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -105,10 +105,17 @@
     public @interface RequestResult {}
 
     /**
+     * The base class of {@link OptionsBuilder} and {@link PresenceBuilder}
+     */
+    public static abstract class RcsUcsCapabilityBuilder {
+        public abstract @NonNull RcsContactUceCapability build();
+    }
+
+    /**
      * Builder to help construct {@link RcsContactUceCapability} instances when capabilities were
      * queried through SIP OPTIONS.
      */
-    public static class OptionsBuilder {
+    public static class OptionsBuilder extends RcsUcsCapabilityBuilder {
 
         private final RcsContactUceCapability mCapabilities;
 
@@ -155,6 +162,7 @@
         /**
          * @return the constructed instance.
          */
+        @Override
         public @NonNull RcsContactUceCapability build() {
             return mCapabilities;
         }
@@ -164,7 +172,7 @@
      * Builder to help construct {@link RcsContactUceCapability} instances when capabilities were
      * queried through a presence server.
      */
-    public static class PresenceBuilder {
+    public static class PresenceBuilder extends RcsUcsCapabilityBuilder {
 
         private final RcsContactUceCapability mCapabilities;
 
@@ -205,6 +213,7 @@
         /**
          * @return the RcsContactUceCapability instance.
          */
+        @Override
         public @NonNull RcsContactUceCapability build() {
             return mCapabilities;
         }
diff --git a/tests/BootImageProfileTest/TEST_MAPPING b/tests/BootImageProfileTest/TEST_MAPPING
new file mode 100644
index 0000000..1b569f9
--- /dev/null
+++ b/tests/BootImageProfileTest/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "BootImageProfileTest"
+    }
+  ]
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 1f03c4d..686ddcb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -105,8 +105,7 @@
                                 configuration.endRotation)
                             navBarLayerIsAlwaysVisible(enabled = false)
                             statusBarLayerIsAlwaysVisible(enabled = false)
-                            visibleLayersShownMoreThanOneConsecutiveEntry(
-                                    enabled = Surface.ROTATION_0 == configuration.endRotation)
+                            visibleLayersShownMoreThanOneConsecutiveEntry(bugId = 174541970)
 
                             appLayerReplacesWallpaperLayer(testApp)
                         }
diff --git a/tools/codegen/src/com/android/codegen/ClassPrinter.kt b/tools/codegen/src/com/android/codegen/ClassPrinter.kt
index b90e1bb..8cae14a 100644
--- a/tools/codegen/src/com/android/codegen/ClassPrinter.kt
+++ b/tools/codegen/src/com/android/codegen/ClassPrinter.kt
@@ -145,7 +145,6 @@
             }
             return when {
                 cliArgs.contains("--hidden-$kebabCase") -> true
-                this == FeatureFlag.BUILD_UPON -> FeatureFlag.BUILDER.hidden
                 else -> false
             }
         }
diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt
index 6a635d0..d9ad649 100644
--- a/tools/codegen/src/com/android/codegen/SharedConstants.kt
+++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt
@@ -1,7 +1,7 @@
 package com.android.codegen
 
 const val CODEGEN_NAME = "codegen"
-const val CODEGEN_VERSION = "1.0.21"
+const val CODEGEN_VERSION = "1.0.22"
 
 const val CANONICAL_BUILDER_CLASS = "Builder"
 const val BASE_BUILDER_CLASS = "BaseBuilder"