Merge changes from topics "dpmapis1", "dpmapis2"

* changes:
  Add DPM Permissions 3.
  Add permissions to DPM 2.
  Add permission access to DPM Apis.
diff --git a/core/api/current.txt b/core/api/current.txt
index e350f56..95729da 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -22527,6 +22527,7 @@
     field @Deprecated public static final int BUFFER_FLAG_SYNC_FRAME = 1; // 0x1
     field public static final int CONFIGURE_FLAG_ENCODE = 1; // 0x1
     field public static final int CONFIGURE_FLAG_USE_BLOCK_MODEL = 2; // 0x2
+    field public static final int CONFIGURE_FLAG_USE_CRYPTO_ASYNC = 4; // 0x4
     field public static final int CRYPTO_MODE_AES_CBC = 2; // 0x2
     field public static final int CRYPTO_MODE_AES_CTR = 1; // 0x1
     field public static final int CRYPTO_MODE_UNENCRYPTED = 0; // 0x0
@@ -22556,6 +22557,7 @@
 
   public abstract static class MediaCodec.Callback {
     ctor public MediaCodec.Callback();
+    method public void onCryptoError(@NonNull android.media.MediaCodec, @NonNull android.media.MediaCodec.CryptoException);
     method public abstract void onError(@NonNull android.media.MediaCodec, @NonNull android.media.MediaCodec.CodecException);
     method public abstract void onInputBufferAvailable(@NonNull android.media.MediaCodec, int);
     method public abstract void onOutputBufferAvailable(@NonNull android.media.MediaCodec, int, @NonNull android.media.MediaCodec.BufferInfo);
@@ -22573,6 +22575,7 @@
 
   public static final class MediaCodec.CryptoException extends java.lang.RuntimeException implements android.media.MediaDrmThrowable {
     ctor public MediaCodec.CryptoException(int, @Nullable String);
+    method @Nullable public android.media.MediaCodec.CryptoInfo getCryptoInfo();
     method public int getErrorCode();
     field @Deprecated public static final int ERROR_FRAME_TOO_LARGE = 8; // 0x8
     field @Deprecated public static final int ERROR_INSUFFICIENT_OUTPUT_PROTECTION = 4; // 0x4
@@ -51725,6 +51728,7 @@
     ctor public SurfaceView(android.content.Context, android.util.AttributeSet);
     ctor public SurfaceView(android.content.Context, android.util.AttributeSet, int);
     ctor public SurfaceView(android.content.Context, android.util.AttributeSet, int, int);
+    method public void applyTransactionToFrame(@NonNull android.view.SurfaceControl.Transaction);
     method public android.view.SurfaceHolder getHolder();
     method @Nullable public android.os.IBinder getHostToken();
     method public android.view.SurfaceControl getSurfaceControl();
diff --git a/core/java/android/service/notification/OWNERS b/core/java/android/service/notification/OWNERS
index debb493..bb0e6ab 100644
--- a/core/java/android/service/notification/OWNERS
+++ b/core/java/android/service/notification/OWNERS
@@ -1,6 +1,7 @@
 # Bug component: 34005
 
 juliacr@google.com
-pixel@google.com
+yurilin@google.com
+jeffdq@google.com
 dsandler@android.com
 dsandler@google.com
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 0bce710..b46a68c 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1756,8 +1756,14 @@
     };
 
     /**
-     * Return a SurfaceControl which can be used for parenting Surfaces to
-     * this SurfaceView.
+     * Return a SurfaceControl which can be used for parenting Surfaces to this SurfaceView.
+     *
+     * Note that this SurfaceControl is effectively read-only. Its only well-defined usage is in
+     * using the SurfaceControl as a parent for an application's hierarchy of SurfaceControls. All
+     * other properties of the SurfaceControl, such as its position, may be mutated by the
+     * SurfaceView at any time which will override what the application is requesting. Do not apply
+     * any {@link SurfaceControl.Transaction} to this SurfaceControl except for reparenting
+     * child SurfaceControls. See: {@link SurfaceControl.Transaction#reparent}.
      *
      * @return The SurfaceControl for this SurfaceView.
      */
@@ -1983,4 +1989,32 @@
     public void syncNextFrame(Consumer<Transaction> t) {
         mBlastBufferQueue.syncNextTransaction(t);
     }
+
+    /**
+     * Adds a transaction that would be applied synchronously with displaying the SurfaceView's next
+     * frame.
+     *
+     * Note that the exact frame that the transaction is applied with is only well-defined when
+     * SurfaceView rendering is paused prior to calling applyTransactionToFrame(), so that the
+     * transaction is applied with the next frame rendered after applyTransactionToFrame() is
+     * called. If frames are continuously rendering to the SurfaceView when
+     * applyTransactionToFrame() is called, then it is undefined which frame the transaction is
+     * applied with. It is also possible for the transaction to not be applied if no new frames are
+     * rendered to the SurfaceView after this is called.
+     *
+     * @param transaction The transaction to apply. The system takes ownership of the transaction
+     *                    and promises to eventually apply the transaction.
+     * @throws IllegalStateException if the underlying Surface does not exist (and therefore
+     *         there is no next frame).
+     */
+    public void applyTransactionToFrame(@NonNull SurfaceControl.Transaction transaction) {
+        synchronized (mSurfaceControlLock) {
+            if (mBlastBufferQueue == null) {
+                throw new IllegalStateException("Surface does not exist!");
+            }
+
+            long frameNumber = mBlastBufferQueue.getLastAcquiredFrameNum() + 1;
+            mBlastBufferQueue.mergeWithNextTransaction(transaction, frameNumber);
+        }
+    }
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 480abe0..ff0e326 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -10718,7 +10718,8 @@
         public int ensureDirectConnection() {
             if (mDirectConnectionId == AccessibilityNodeInfo.UNDEFINED_CONNECTION_ID) {
                 mDirectConnectionId = AccessibilityInteractionClient.addDirectConnection(
-                        new AccessibilityInteractionConnection(ViewRootImpl.this));
+                        new AccessibilityInteractionConnection(ViewRootImpl.this),
+                        mAccessibilityManager);
                 // Notify listeners in the app process.
                 mAccessibilityManager.notifyAccessibilityStateChanged();
             }
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 3da3ab9..d9fcfb5 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -275,7 +275,8 @@
      *
      * @param connection The ViewRootImpl's {@link IAccessibilityInteractionConnection}.
      */
-    public static int addDirectConnection(IAccessibilityInteractionConnection connection) {
+    public static int addDirectConnection(IAccessibilityInteractionConnection connection,
+            AccessibilityManager accessibilityManager) {
         synchronized (sConnectionCache) {
             int connectionId = sDirectConnectionIdCounter++;
             if (getConnection(connectionId) != null) {
@@ -283,7 +284,7 @@
                         "Cannot add direct connection with existing id " + connectionId);
             }
             DirectAccessibilityConnection directAccessibilityConnection =
-                    new DirectAccessibilityConnection(connection);
+                    new DirectAccessibilityConnection(connection, accessibilityManager);
             sConnectionCache.put(connectionId, directAccessibilityConnection);
             sDirectConnectionCount++;
             // Do not use AccessibilityCache for this connection, since there is no corresponding
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index d405c0b..aa631cf 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -2431,4 +2431,31 @@
             return true;
         }
     }
+
+    /**
+     * Retrieves the window's transformation matrix and magnification spec.
+     *
+     * <p>
+     * Used by callers outside of the AccessibilityManagerService process which need
+     * this information, like {@link android.view.accessibility.DirectAccessibilityConnection}.
+     * </p>
+     *
+     * @return The transformation spec
+     * @hide
+     */
+    public IAccessibilityManager.WindowTransformationSpec getWindowTransformationSpec(
+            int windowId) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return null;
+            }
+        }
+        try {
+            return service.getWindowTransformationSpec(windowId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/view/accessibility/DirectAccessibilityConnection.java b/core/java/android/view/accessibility/DirectAccessibilityConnection.java
index dcc5660..8a3bb6f 100644
--- a/core/java/android/view/accessibility/DirectAccessibilityConnection.java
+++ b/core/java/android/view/accessibility/DirectAccessibilityConnection.java
@@ -17,12 +17,10 @@
 package android.view.accessibility;
 
 import android.accessibilityservice.IAccessibilityServiceConnection;
-import android.graphics.Matrix;
 import android.graphics.Region;
 import android.os.Bundle;
 import android.os.Process;
 import android.os.RemoteException;
-import android.view.MagnificationSpec;
 
 /**
  * Minimal {@link IAccessibilityServiceConnection} implementation that interacts
@@ -51,24 +49,21 @@
  */
 class DirectAccessibilityConnection extends IAccessibilityServiceConnection.Default {
     private final IAccessibilityInteractionConnection mAccessibilityInteractionConnection;
+    private final AccessibilityManager mAccessibilityManager;
 
     // Fetch all views, but do not use prefetching/cache since this "connection" does not
     // receive cache invalidation events (as it is not linked to an AccessibilityService).
     private static final int FETCH_FLAGS =
             AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS
                     | AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS;
-    private static final MagnificationSpec MAGNIFICATION_SPEC = new MagnificationSpec();
     private static final int PID = Process.myPid();
     private static final Region INTERACTIVE_REGION = null;
-    private static final float[] TRANSFORM_MATRIX = new float[9];
-
-    static {
-        Matrix.IDENTITY_MATRIX.getValues(TRANSFORM_MATRIX);
-    }
 
     DirectAccessibilityConnection(
-            IAccessibilityInteractionConnection accessibilityInteractionConnection) {
+            IAccessibilityInteractionConnection accessibilityInteractionConnection,
+            AccessibilityManager accessibilityManager) {
         mAccessibilityInteractionConnection = accessibilityInteractionConnection;
+        mAccessibilityManager = accessibilityManager;
     }
 
     @Override
@@ -76,9 +71,11 @@
             long accessibilityNodeId, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, int flags, long threadId,
             Bundle arguments) throws RemoteException {
+        IAccessibilityManager.WindowTransformationSpec spec =
+                mAccessibilityManager.getWindowTransformationSpec(accessibilityWindowId);
         mAccessibilityInteractionConnection.findAccessibilityNodeInfoByAccessibilityId(
                 accessibilityNodeId, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID,
-                threadId, MAGNIFICATION_SPEC, TRANSFORM_MATRIX, arguments);
+                threadId, spec.magnificationSpec, spec.transformationMatrix, arguments);
         return new String[0];
     }
 
@@ -87,9 +84,11 @@
             long accessibilityNodeId, String text, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, long threadId)
             throws RemoteException {
+        IAccessibilityManager.WindowTransformationSpec spec =
+                mAccessibilityManager.getWindowTransformationSpec(accessibilityWindowId);
         mAccessibilityInteractionConnection.findAccessibilityNodeInfosByText(accessibilityNodeId,
                 text, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
-                MAGNIFICATION_SPEC, TRANSFORM_MATRIX);
+                spec.magnificationSpec, spec.transformationMatrix);
         return new String[0];
     }
 
@@ -98,9 +97,11 @@
             long accessibilityNodeId, String viewId, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, long threadId)
             throws RemoteException {
+        IAccessibilityManager.WindowTransformationSpec spec =
+                mAccessibilityManager.getWindowTransformationSpec(accessibilityWindowId);
         mAccessibilityInteractionConnection.findAccessibilityNodeInfosByViewId(accessibilityNodeId,
                 viewId, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
-                MAGNIFICATION_SPEC, TRANSFORM_MATRIX);
+                spec.magnificationSpec, spec.transformationMatrix);
         return new String[0];
     }
 
@@ -108,9 +109,11 @@
     public String[] findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType,
             int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId)
             throws RemoteException {
+        IAccessibilityManager.WindowTransformationSpec spec =
+                mAccessibilityManager.getWindowTransformationSpec(accessibilityWindowId);
         mAccessibilityInteractionConnection.findFocus(accessibilityNodeId, focusType,
                 INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
-                MAGNIFICATION_SPEC, TRANSFORM_MATRIX);
+                spec.magnificationSpec, spec.transformationMatrix);
         return new String[0];
     }
 
@@ -118,9 +121,11 @@
     public String[] focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction,
             int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId)
             throws RemoteException {
+        IAccessibilityManager.WindowTransformationSpec spec =
+                mAccessibilityManager.getWindowTransformationSpec(accessibilityWindowId);
         mAccessibilityInteractionConnection.focusSearch(accessibilityNodeId, direction,
                 INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
-                MAGNIFICATION_SPEC, TRANSFORM_MATRIX);
+                spec.magnificationSpec, spec.transformationMatrix);
         return new String[0];
     }
 
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 098e97c..1302421 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -29,6 +29,7 @@
 import android.view.accessibility.IWindowMagnificationConnection;
 import android.view.InputEvent;
 import android.view.IWindow;
+import android.view.MagnificationSpec;
 
 /**
  * Interface implemented by the AccessibilityManagerService called by
@@ -124,4 +125,10 @@
     boolean startFlashNotificationSequence(String opPkg, int reason, IBinder token);
     boolean stopFlashNotificationSequence(String opPkg);
     boolean startFlashNotificationEvent(String opPkg, int reason, String reasonPkg);
+
+    parcelable WindowTransformationSpec {
+        float[] transformationMatrix;
+        MagnificationSpec magnificationSpec;
+    }
+    WindowTransformationSpec getWindowTransformationSpec(int windowId);
 }
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index df9c539..2541a506 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -571,6 +571,10 @@
    void onError(&hellip;) {
      &hellip;
    }
+   {@literal @Override}
+   void onCryptoError(&hellip;) {
+     &hellip;
+   }
  });
  codec.configure(format, &hellip;);
  mOutputFormat = codec.getOutputFormat(); // option B
@@ -1774,6 +1778,7 @@
     private static final int CB_OUTPUT_FORMAT_CHANGE = 4;
     private static final String EOS_AND_DECODE_ONLY_ERROR_MESSAGE = "An input buffer cannot have "
             + "both BUFFER_FLAG_END_OF_STREAM and BUFFER_FLAG_DECODE_ONLY flags";
+    private static final int CB_CRYPTO_ERROR = 6;
 
     private class EventHandler extends Handler {
         private MediaCodec mCodec;
@@ -1901,6 +1906,12 @@
                     break;
                 }
 
+                case CB_CRYPTO_ERROR:
+                {
+                    mCallback.onCryptoError(mCodec, (MediaCodec.CryptoException) msg.obj);
+                    break;
+                }
+
                 case CB_OUTPUT_FORMAT_CHANGE:
                 {
                     mCallback.onOutputFormatChanged(mCodec,
@@ -2104,12 +2115,25 @@
      */
     public static final int CONFIGURE_FLAG_USE_BLOCK_MODEL = 2;
 
+    /**
+     * This flag should be used on a secure decoder only. MediaCodec configured with this
+     * flag does decryption in a separate thread. The flag requires MediaCodec to operate
+     * asynchronously and will throw CryptoException if any, in the onCryptoError()
+     * callback. Applications should override the default implementation of
+     * onCryptoError() and access the associated CryptoException.
+     *
+     * CryptoException thrown will contain {@link MediaCodec.CryptoInfo}
+     * This can be accessed using getCryptoInfo()
+     */
+    public static final int CONFIGURE_FLAG_USE_CRYPTO_ASYNC = 4;
+
     /** @hide */
     @IntDef(
         flag = true,
         value = {
             CONFIGURE_FLAG_ENCODE,
             CONFIGURE_FLAG_USE_BLOCK_MODEL,
+            CONFIGURE_FLAG_USE_CRYPTO_ASYNC,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ConfigureFlag {}
@@ -2523,19 +2547,20 @@
     public final static class CryptoException extends RuntimeException
             implements MediaDrmThrowable {
         public CryptoException(int errorCode, @Nullable String detailMessage) {
-            this(detailMessage, errorCode, 0, 0, 0);
+            this(detailMessage, errorCode, 0, 0, 0, null);
         }
 
         /**
          * @hide
          */
         public CryptoException(String message, int errorCode, int vendorError, int oemError,
-                int errorContext) {
+                int errorContext, @Nullable CryptoInfo cryptoInfo) {
             super(message);
             mErrorCode = errorCode;
             mVendorError = vendorError;
             mOemError = oemError;
             mErrorContext = errorContext;
+            mCryptoInfo = cryptoInfo;
         }
 
         /**
@@ -2654,6 +2679,16 @@
             return mErrorCode;
         }
 
+        /**
+         * Returns CryptoInfo associated with this {@link CryptoException}
+         * if any
+         *
+         * @return CryptoInfo object if any. {@link MediaCodec.CryptoException}
+         */
+        public @Nullable CryptoInfo getCryptoInfo() {
+            return mCryptoInfo;
+        }
+
         @Override
         public int getVendorError() {
             return mVendorError;
@@ -2670,6 +2705,7 @@
         }
 
         private final int mErrorCode, mVendorError, mOemError, mErrorContext;
+        private CryptoInfo mCryptoInfo;
     }
 
     /**
@@ -5088,6 +5124,25 @@
         public abstract void onError(@NonNull MediaCodec codec, @NonNull CodecException e);
 
         /**
+         * Called only when MediaCodec encountered a crypto(decryption) error when using
+         * a decoder configured with CONFIGURE_FLAG_USE_CRYPTO_ASYNC flag along with crypto
+         * or descrambler object.
+         *
+         * @param codec The MediaCodec object
+         * @param e The {@link MediaCodec.CryptoException} object with error details.
+         */
+        public void onCryptoError(@NonNull MediaCodec codec, @NonNull CryptoException e) {
+            /*
+             * A default implementation for backward compatibility.
+             * Use of CONFIGURE_FLAG_USE_CRYPTO_ASYNC requires override of this callback
+             * to receive CrytoInfo. Without an orverride an exception is thrown.
+             */
+            throw new IllegalStateException(
+                    "Client must override onCryptoError when the codec is " +
+                    "configured with CONFIGURE_FLAG_USE_CRYPTO_ASYNC.", e);
+        }
+
+        /**
          * Called when the output format has changed
          *
          * @param codec The MediaCodec object.
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 5b0c2a2..9a4aa33 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -184,6 +184,8 @@
     jmethodID postEventFromNativeID;
     jmethodID lockAndGetContextID;
     jmethodID setAndUnlockContextID;
+    jmethodID cryptoInfoSetID;
+    jmethodID cryptoInfoSetPatternID;
     jfieldID cryptoInfoNumSubSamplesID;
     jfieldID cryptoInfoNumBytesOfClearDataID;
     jfieldID cryptoInfoNumBytesOfEncryptedDataID;
@@ -203,6 +205,7 @@
 static fields_t gFields;
 static const void *sRefBaseOwner;
 
+jint MediaErrorToJavaError(status_t err);
 
 ////////////////////////////////////////////////////////////////////////////////
 
@@ -1068,6 +1071,180 @@
     return (jthrowable)env->NewObject(clazz.get(), ctor, err, actionCode, msgObj.get());
 }
 
+static void AMessageToCryptoInfo(JNIEnv * env, const jobject & obj,
+        const sp<AMessage> & msg) {
+    if(msg == nullptr || obj == nullptr) {
+        ALOGE("CryptoAsync Nothing to do in AMessagetoCryptoInfo");
+        return;
+    }
+    size_t numSubSamples = 0;
+    sp<ABuffer> subSamplesBuffer;
+    sp<ABuffer> keyBuffer;
+    sp<ABuffer> ivBuffer;
+    CryptoPlugin::Mode mode;
+    CryptoPlugin::Pattern pattern;
+    CHECK(msg->findInt32("mode", (int*)&mode));
+    CHECK(msg->findSize("numSubSamples", &numSubSamples));
+    CHECK(msg->findBuffer("subSamples", &subSamplesBuffer));
+    CHECK(msg->findInt32("encryptBlocks", (int32_t *)&pattern.mEncryptBlocks));
+    CHECK(msg->findInt32("skipBlocks", (int32_t *)&pattern.mSkipBlocks));
+    CHECK(msg->findBuffer("iv", &ivBuffer));
+    CHECK(msg->findBuffer("key", &keyBuffer));
+
+    // subsamples
+    ScopedLocalRef<jintArray> samplesOfEncryptedDataArr(env, env->NewIntArray(numSubSamples));
+    ScopedLocalRef<jintArray> samplesOfClearDataArr(env, env->NewIntArray(numSubSamples));
+    jboolean isCopy;
+    jint *dstEncryptedSamples =
+        env->GetIntArrayElements(samplesOfEncryptedDataArr.get(), &isCopy);
+    jint * dstClearSamples =
+        env->GetIntArrayElements(samplesOfClearDataArr.get(), &isCopy);
+
+    CryptoPlugin::SubSample * samplesArray =
+        (CryptoPlugin::SubSample*)(subSamplesBuffer.get()->data());
+
+    for(int i = 0 ; i < numSubSamples ; i++) {
+        dstEncryptedSamples[i] = samplesArray[i].mNumBytesOfEncryptedData;
+        dstClearSamples[i] = samplesArray[i].mNumBytesOfClearData;
+    }
+    env->ReleaseIntArrayElements(samplesOfEncryptedDataArr.get(), dstEncryptedSamples, 0);
+    env->ReleaseIntArrayElements(samplesOfClearDataArr.get(), dstClearSamples, 0);
+    // key and iv
+    jbyteArray keyArray = NULL;
+    jbyteArray ivArray = NULL;
+    if (keyBuffer.get() != nullptr && keyBuffer->size() > 0) {
+        keyArray = env->NewByteArray(keyBuffer->size());
+        jbyte * dstKey = env->GetByteArrayElements(keyArray, &isCopy);
+        memcpy(dstKey, keyBuffer->data(), keyBuffer->size());
+        env->ReleaseByteArrayElements(keyArray,dstKey,0);
+    }
+    if (ivBuffer.get() != nullptr && ivBuffer->size() > 0) {
+        ivArray = env->NewByteArray(ivBuffer->size());
+        jbyte *dstIv = env->GetByteArrayElements(ivArray, &isCopy);
+        memcpy(dstIv, ivBuffer->data(), ivBuffer->size());
+        env->ReleaseByteArrayElements(ivArray, dstIv,0);
+    }
+    // set samples, key and iv
+    env->CallVoidMethod(
+        obj,
+        gFields.cryptoInfoSetID,
+        (jint)numSubSamples,
+        samplesOfClearDataArr.get(),
+        samplesOfEncryptedDataArr.get(),
+        keyArray,
+        ivArray,
+        mode);
+    if (keyArray != NULL) {
+        env->DeleteLocalRef(keyArray);
+    }
+    if (ivArray != NULL) {
+        env->DeleteLocalRef(ivArray);
+    }
+    // set pattern
+    env->CallVoidMethod(
+        obj,
+        gFields.cryptoInfoSetPatternID,
+        pattern.mEncryptBlocks,
+        pattern.mSkipBlocks);
+}
+
+static void CryptoErrorToJavaError(status_t err, jint& jerr, std::string& defaultMsg) {
+    switch(err) {
+        case ERROR_DRM_NO_LICENSE:
+            jerr = gCryptoErrorCodes.cryptoErrorNoKey;
+            defaultMsg = "Crypto key not available";
+            break;
+        case ERROR_DRM_LICENSE_EXPIRED:
+            jerr = gCryptoErrorCodes.cryptoErrorKeyExpired;
+            defaultMsg = "License expired";
+            break;
+        case ERROR_DRM_RESOURCE_BUSY:
+            jerr = gCryptoErrorCodes.cryptoErrorResourceBusy;
+            defaultMsg = "Resource busy or unavailable";
+            break;
+        case ERROR_DRM_INSUFFICIENT_OUTPUT_PROTECTION:
+            jerr = gCryptoErrorCodes.cryptoErrorInsufficientOutputProtection;
+            defaultMsg = "Required output protections are not active";
+            break;
+        case ERROR_DRM_SESSION_NOT_OPENED:
+            jerr = gCryptoErrorCodes.cryptoErrorSessionNotOpened;
+            defaultMsg = "Attempted to use a closed session";
+            break;
+        case ERROR_DRM_INSUFFICIENT_SECURITY:
+            jerr = gCryptoErrorCodes.cryptoErrorInsufficientSecurity;
+            defaultMsg = "Required security level is not met";
+            break;
+        case ERROR_DRM_CANNOT_HANDLE:
+            jerr = gCryptoErrorCodes.cryptoErrorUnsupportedOperation;
+            defaultMsg = "Operation not supported in this configuration";
+            break;
+        case ERROR_DRM_FRAME_TOO_LARGE:
+            jerr = gCryptoErrorCodes.cryptoErrorFrameTooLarge;
+            defaultMsg = "Decrytped frame exceeds size of output buffer";
+            break;
+        case ERROR_DRM_SESSION_LOST_STATE:
+            jerr = gCryptoErrorCodes.cryptoErrorLostState;
+            defaultMsg = "Session state was lost, open a new session and retry";
+            break;
+        default:  // Other negative DRM error codes go out best-effort.
+            jerr = MediaErrorToJavaError(err);
+            defaultMsg = StrCryptoError(err);
+            break;
+    }
+}
+static jthrowable createCryptoException(JNIEnv *env, status_t err,
+        const char * msg = NULL, const sp<ICrypto> & crypto = NULL,
+    const sp<AMessage> & cryptoInfo = NULL) {
+    jthrowable exception = nullptr;
+    jmethodID constructID = nullptr;
+    ScopedLocalRef<jobject> cryptoInfoObject(env);
+    std::string defaultMsg = "Unknown Error";
+    jint jerr = 0;
+    // Get a class ref for CryptoException
+    ScopedLocalRef<jclass> clazz(
+        env, env->FindClass("android/media/MediaCodec$CryptoException"));
+    CHECK(clazz.get() != NULL);
+
+    // Get constructor ref for CryptoException
+    constructID = env->GetMethodID(clazz.get(), "<init>",
+            "(Ljava/lang/String;IIIILandroid/media/MediaCodec$CryptoInfo;)V");
+    CHECK(constructID != NULL);
+
+    // create detailed message for exception
+    CryptoErrorToJavaError(err, jerr, defaultMsg);
+    std::string originalMsg(msg != NULL ? msg : defaultMsg.c_str());
+    DrmStatus dStatus(err, originalMsg.c_str());
+    std::string detailedMsg(
+            DrmUtils::GetExceptionMessage(dStatus, defaultMsg.c_str(), crypto));
+    jstring msgObj = env->NewStringUTF(detailedMsg.c_str());
+
+    if (cryptoInfo != nullptr) {
+        // Class ref for CryptoInfo
+        ScopedLocalRef<jclass> clazzCryptoInfo(
+                env, env->FindClass("android/media/MediaCodec$CryptoInfo"));
+        CHECK(clazzCryptoInfo.get() != NULL);
+
+        // Constructor reference for CryptoInfo
+        jmethodID constructCryptoInfo =
+                env->GetMethodID(clazzCryptoInfo.get(), "<init>", "()V");
+        CHECK(constructCryptoInfo != NULL);
+
+        // Create CryptoInfo jobject
+        cryptoInfoObject.reset(
+                env->NewObject(clazzCryptoInfo.get(), constructCryptoInfo));
+        CHECK(cryptoInfoObject.get() != NULL);
+
+        // Translate AMesage to CryptoInfo
+        AMessageToCryptoInfo(env, cryptoInfoObject.get(), cryptoInfo);
+    }
+
+    exception = (jthrowable)env->NewObject(
+            clazz.get(), constructID, msgObj, jerr,
+            dStatus.getCdmErr(), dStatus.getOemErr(), dStatus.getContext(),
+            cryptoInfoObject.get());
+
+    return exception;
+}
 void JMediaCodec::handleCallback(const sp<AMessage> &msg) {
     int32_t arg1, arg2 = 0;
     jobject obj = NULL;
@@ -1107,6 +1284,17 @@
             break;
         }
 
+        case MediaCodec::CB_CRYPTO_ERROR:
+        {
+            int32_t err, actionCode;
+            AString errorDetail;
+            CHECK(msg->findInt32("err", &err));
+            CHECK(msg->findInt32("actionCode",&actionCode));
+            CHECK(msg->findString("errorDetail", &errorDetail));
+            obj = (jobject)createCryptoException(env, err, errorDetail.c_str(), NULL, msg);
+            break;
+        }
+
         case MediaCodec::CB_ERROR:
         {
             int32_t err, actionCode;
@@ -1144,7 +1332,6 @@
         default:
             TRESPASS();
     }
-
     env->CallVoidMethod(
             mObject,
             gFields.postEventFromNativeID,
@@ -1229,7 +1416,6 @@
     }
 }
 
-jint MediaErrorToJavaError(status_t err);
 
 }  // namespace android
 
@@ -1280,70 +1466,8 @@
 
 static void throwCryptoException(JNIEnv *env, status_t err, const char *msg,
         const sp<ICrypto> &crypto) {
-    ScopedLocalRef<jclass> clazz(
-            env, env->FindClass("android/media/MediaCodec$CryptoException"));
-    CHECK(clazz.get() != NULL);
-
-    jmethodID constructID =
-        env->GetMethodID(clazz.get(), "<init>", "(Ljava/lang/String;IIII)V");
-    CHECK(constructID != NULL);
-
-    std::string defaultMsg = "Unknown Error";
-
-    /* translate OS errors to Java API CryptoException errorCodes (which are positive) */
-    jint jerr = 0;
-    switch (err) {
-        case ERROR_DRM_NO_LICENSE:
-            jerr = gCryptoErrorCodes.cryptoErrorNoKey;
-            defaultMsg = "Crypto key not available";
-            break;
-        case ERROR_DRM_LICENSE_EXPIRED:
-            jerr = gCryptoErrorCodes.cryptoErrorKeyExpired;
-            defaultMsg = "License expired";
-            break;
-        case ERROR_DRM_RESOURCE_BUSY:
-            jerr = gCryptoErrorCodes.cryptoErrorResourceBusy;
-            defaultMsg = "Resource busy or unavailable";
-            break;
-        case ERROR_DRM_INSUFFICIENT_OUTPUT_PROTECTION:
-            jerr = gCryptoErrorCodes.cryptoErrorInsufficientOutputProtection;
-            defaultMsg = "Required output protections are not active";
-            break;
-        case ERROR_DRM_SESSION_NOT_OPENED:
-            jerr = gCryptoErrorCodes.cryptoErrorSessionNotOpened;
-            defaultMsg = "Attempted to use a closed session";
-            break;
-        case ERROR_DRM_INSUFFICIENT_SECURITY:
-            jerr = gCryptoErrorCodes.cryptoErrorInsufficientSecurity;
-            defaultMsg = "Required security level is not met";
-            break;
-        case ERROR_DRM_CANNOT_HANDLE:
-            jerr = gCryptoErrorCodes.cryptoErrorUnsupportedOperation;
-            defaultMsg = "Operation not supported in this configuration";
-            break;
-        case ERROR_DRM_FRAME_TOO_LARGE:
-            jerr = gCryptoErrorCodes.cryptoErrorFrameTooLarge;
-            defaultMsg = "Decrytped frame exceeds size of output buffer";
-            break;
-        case ERROR_DRM_SESSION_LOST_STATE:
-            jerr = gCryptoErrorCodes.cryptoErrorLostState;
-            defaultMsg = "Session state was lost, open a new session and retry";
-            break;
-        default:  /* Other negative DRM error codes go out best-effort. */
-            jerr = MediaErrorToJavaError(err);
-            defaultMsg = StrCryptoError(err);
-            break;
-    }
-
-    std::string originalMsg(msg != NULL ? msg : defaultMsg.c_str());
-    DrmStatus dStatus(err, originalMsg.c_str());
-    std::string detailedMsg(DrmUtils::GetExceptionMessage(dStatus, defaultMsg.c_str(), crypto));
-    jstring msgObj = env->NewStringUTF(detailedMsg.c_str());
-
-    jthrowable exception =
-        (jthrowable)env->NewObject(clazz.get(), constructID, msgObj, jerr,
-                                   dStatus.getCdmErr(), dStatus.getOemErr(), dStatus.getContext());
-
+    jthrowable exception = createCryptoException(
+            env, err, msg, crypto, /* cryptoInfo */ NULL);
     env->Throw(exception);
 }
 
@@ -2963,6 +3087,12 @@
     clazz.reset(env->FindClass("android/media/MediaCodec$CryptoInfo"));
     CHECK(clazz.get() != NULL);
 
+    gFields.cryptoInfoSetID = env->GetMethodID(clazz.get(), "set", "(I[I[I[B[BI)V");
+    CHECK(gFields.cryptoInfoSetID != NULL);
+
+    gFields.cryptoInfoSetPatternID = env->GetMethodID(clazz.get(), "setPattern", "(II)V");
+    CHECK(gFields.cryptoInfoSetPatternID != NULL);
+
     gFields.cryptoInfoNumSubSamplesID =
         env->GetFieldID(clazz.get(), "numSubSamples", "I");
     CHECK(gFields.cryptoInfoNumSubSamplesID != NULL);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 85e050c..ce7f0a1 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -100,7 +100,7 @@
     // TODO(b/260335638): Tracking Bug
     @JvmField
     val NOTIFICATION_INLINE_REPLY_ANIMATION =
-        unreleasedFlag(174148361, "notification_inline_reply_animation", teamfood = true)
+        releasedFlag(174148361, "notification_inline_reply_animation")
 
     val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
         releasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 5dd532a..9d6230b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -1346,9 +1346,9 @@
         if (keyguardUpdateMonitor.isUserInLockdown(removed.userId)) {
             logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
         } else if (useMediaResumption && removed.resumeAction != null && removed.isLocalSession()) {
-            convertToResumePlayer(removed)
+            convertToResumePlayer(key, removed)
         } else if (mediaFlags.isRetainingPlayersEnabled()) {
-            handlePossibleRemoval(removed, notificationRemoved = true)
+            handlePossibleRemoval(key, removed, notificationRemoved = true)
         } else {
             notifyMediaDataRemoved(key)
             logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
@@ -1362,7 +1362,7 @@
         val entry = mediaEntries.remove(key) ?: return
         // Clear token since the session is no longer valid
         val updated = entry.copy(token = null)
-        handlePossibleRemoval(updated)
+        handlePossibleRemoval(key, updated)
     }
 
     /**
@@ -1371,8 +1371,11 @@
      * if it was removed before becoming inactive. (Assumes that [removed] was removed from
      * [mediaEntries] before this function was called)
      */
-    private fun handlePossibleRemoval(removed: MediaData, notificationRemoved: Boolean = false) {
-        val key = removed.notificationKey!!
+    private fun handlePossibleRemoval(
+        key: String,
+        removed: MediaData,
+        notificationRemoved: Boolean = false
+    ) {
         val hasSession = removed.token != null
         if (hasSession && removed.semanticActions != null) {
             // The app was using session actions, and the session is still valid: keep player
@@ -1398,13 +1401,12 @@
                         "($hasSession) gone for inactive player $key"
                 )
             }
-            convertToResumePlayer(removed)
+            convertToResumePlayer(key, removed)
         }
     }
 
     /** Set the given [MediaData] as a resume state player and notify listeners */
-    private fun convertToResumePlayer(data: MediaData) {
-        val key = data.notificationKey!!
+    private fun convertToResumePlayer(key: String, data: MediaData) {
         if (DEBUG) Log.d(TAG, "Converting $key to resume")
         // Move to resume key (aka package name) if that key doesn't already exist.
         val resumeAction = data.resumeAction?.let { getResumeMediaAction(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 6bf7668..82bd45c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.os.UserHandle
@@ -39,14 +41,20 @@
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.transformLatest
 import kotlinx.coroutines.launch
 
 /**
@@ -93,14 +101,39 @@
     private suspend fun trackUnseenNotificationsWhileUnlocked() {
         // Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is
         // showing again
+        var clearUnseenOnUnlock = false
         keyguardRepository.isKeyguardShowing.collectLatest { isKeyguardShowing ->
-            if (!isKeyguardShowing) {
+            if (isKeyguardShowing) {
+                // Wait for the user to spend enough time on the lock screen before clearing unseen
+                // set when unlocked
+                awaitTimeSpentNotDozing(SEEN_TIMEOUT)
+                clearUnseenOnUnlock = true
+            } else {
                 unseenNotifFilter.invalidateList("keyguard no longer showing")
+                if (clearUnseenOnUnlock) {
+                    clearUnseenOnUnlock = false
+                    unseenNotifications.clear()
+                }
                 trackUnseenNotifications()
             }
         }
     }
 
+    private suspend fun awaitTimeSpentNotDozing(duration: Duration) {
+        keyguardRepository.isDozing
+            // Use transformLatest so that the timeout delay is cancelled if the device enters doze,
+            // and is restarted when doze ends.
+            .transformLatest { isDozing ->
+                if (!isDozing) {
+                    delay(duration)
+                    // Signal timeout has completed
+                    emit(Unit)
+                }
+            }
+            // Suspend until the first emission
+            .first()
+    }
+
     private suspend fun trackUnseenNotifications() {
         coroutineScope {
             launch { clearUnseenNotificationsWhenShadeIsExpanded() }
@@ -240,5 +273,6 @@
 
     companion object {
         private const val TAG = "KeyguardCoordinator"
+        private val SEEN_TIMEOUT = 5.seconds
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 9f12329..a07a714 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -1897,6 +1897,20 @@
             .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
     }
 
+    @Test
+    fun testSessionDestroyed_noNotificationKey_stillRemoved() {
+        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+
+        // When a notiifcation is added and then removed before it is fully processed
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        backgroundExecutor.runAllReady()
+        mediaDataManager.onNotificationRemoved(KEY)
+
+        // We still make sure to remove it
+        verify(listener).onMediaDataRemoved(eq(KEY))
+    }
+
     /** Helper function to add a media notification and capture the resulting MediaData */
     private fun addNotificationAndLoad() {
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 49da848..8109e24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -23,6 +23,7 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.advanceTimeBy
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
@@ -311,17 +312,20 @@
     fun unseenNotificationIsMarkedAsSeenWhenKeyguardGoesAway() {
         whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
 
-        // GIVEN: Keyguard is showing, unseen notification is present
+        // GIVEN: Keyguard is showing, not dozing, unseen notification is present
         keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setDozing(false)
         runKeyguardCoordinatorTest {
             val fakeEntry = NotificationEntryBuilder().build()
             collectionListener.onEntryAdded(fakeEntry)
 
+            // WHEN: five seconds have passed
+            testScheduler.advanceTimeBy(5.seconds)
+            testScheduler.runCurrent()
+
             // WHEN: Keyguard is no longer showing
             keyguardRepository.setKeyguardShowing(false)
-
-            // When: Shade is expanded
-            statusBarStateListener.onExpandedChanged(true)
+            testScheduler.runCurrent()
 
             // WHEN: Keyguard is shown again
             keyguardRepository.setKeyguardShowing(true)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/TestCoroutineSchedulerUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/TestCoroutineSchedulerUtils.kt
new file mode 100644
index 0000000..84e2a5c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/TestCoroutineSchedulerUtils.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.coroutines
+
+import kotlin.time.Duration
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScheduler
+
+/**
+ * Moves the virtual clock of this dispatcher forward by the specified [Duration].
+ *
+ * @see [TestCoroutineScheduler.advanceTimeBy]
+ */
+fun TestCoroutineScheduler.advanceTimeBy(duration: Duration) {
+    advanceTimeBy(duration.inWholeMilliseconds)
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 311e43a..72c5598 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -529,6 +529,18 @@
     }
 
     @Override
+    public IAccessibilityManager.WindowTransformationSpec getWindowTransformationSpec(
+            int windowId) {
+        IAccessibilityManager.WindowTransformationSpec windowTransformationSpec =
+                new IAccessibilityManager.WindowTransformationSpec();
+        Pair<float[], MagnificationSpec> result =
+                getWindowTransformationMatrixAndMagnificationSpec(windowId);
+        windowTransformationSpec.transformationMatrix = result.first;
+        windowTransformationSpec.magnificationSpec = result.second;
+        return windowTransformationSpec;
+    }
+
+    @Override
     public void onServiceInfoChangedLocked(AccessibilityUserState userState) {
         mSecurityPolicy.onBoundServicesChangedLocked(userState.mUserId,
                 userState.mBoundServices);
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 58c814a..e3d2189 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -741,6 +741,9 @@
                     final android.hardware.thermal.Temperature[] halRet =
                             shouldFilter ? mInstance.getTemperaturesWithType(type)
                                     : mInstance.getTemperatures();
+                    if (halRet == null) {
+                        return ret;
+                    }
                     for (android.hardware.thermal.Temperature t : halRet) {
                         if (!Temperature.isValidStatus(t.throttlingStatus)) {
                             Slog.e(TAG, "Invalid temperature status " + t.throttlingStatus
@@ -774,6 +777,9 @@
                     final android.hardware.thermal.CoolingDevice[] halRet = shouldFilter
                             ? mInstance.getCoolingDevicesWithType(type)
                             : mInstance.getCoolingDevices();
+                    if (halRet == null) {
+                        return ret;
+                    }
                     for (android.hardware.thermal.CoolingDevice t : halRet) {
                         if (!CoolingDevice.isValidType(t.type)) {
                             Slog.e(TAG, "Invalid cooling device type " + t.type + " from AIDL HAL");
@@ -806,9 +812,14 @@
                     final TemperatureThreshold[] halRet =
                             shouldFilter ? mInstance.getTemperatureThresholdsWithType(type)
                                     : mInstance.getTemperatureThresholds();
-
-                    return Arrays.stream(halRet).filter(t -> t.type == type).collect(
-                            Collectors.toList());
+                    if (halRet == null) {
+                        return ret;
+                    }
+                    if (shouldFilter) {
+                        return Arrays.stream(halRet).filter(t -> t.type == type).collect(
+                                Collectors.toList());
+                    }
+                    return Arrays.asList(halRet);
                 } catch (IllegalArgumentException | IllegalStateException e) {
                     Slog.e(TAG, "Couldn't getTemperatureThresholds due to invalid status", e);
                 } catch (RemoteException e) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java
index 0ae8dfd..aa9d8c6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java
@@ -120,6 +120,34 @@
     }
 
     @Test
+    public void getCurrentTemperatures_aidl() throws RemoteException {
+        android.hardware.thermal.Temperature halT1 = new android.hardware.thermal.Temperature();
+        halT1.type = TemperatureType.MODEM;
+        halT1.name = "test1";
+        halT1.throttlingStatus = ThrottlingSeverity.EMERGENCY;
+        halT1.value = 99.0f;
+        android.hardware.thermal.Temperature halT2 = new android.hardware.thermal.Temperature();
+        halT2.name = "test2";
+        halT2.type = TemperatureType.SOC;
+        halT2.throttlingStatus = ThrottlingSeverity.NONE;
+
+        Mockito.when(mAidlHalMock.getTemperatures()).thenReturn(
+                new android.hardware.thermal.Temperature[]{
+                        halT2, halT1
+                });
+        List<Temperature> ret = mAidlWrapper.getCurrentTemperatures(false, TemperatureType.UNKNOWN);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatures();
+
+        Temperature expectedT1 = new Temperature(halT1.value, halT1.type, halT1.name,
+                halT1.throttlingStatus);
+        Temperature expectedT2 = new Temperature(halT2.value, halT2.type, halT2.name,
+                halT2.throttlingStatus);
+        List<Temperature> expectedRet = List.of(expectedT1, expectedT2);
+        assertTrue("Got temperature list as " + ret + " with different values compared to "
+                + expectedRet, expectedRet.containsAll(ret));
+    }
+
+    @Test
     public void getCurrentTemperatures_withFilter_aidl() throws RemoteException {
         android.hardware.thermal.Temperature halT1 = new android.hardware.thermal.Temperature();
         halT1.type = TemperatureType.MODEM;
@@ -154,6 +182,23 @@
     }
 
     @Test
+    public void getCurrentTemperatures_nullResult_aidl() throws RemoteException {
+        Mockito.when(mAidlHalMock.getTemperaturesWithType(Mockito.anyInt())).thenReturn(null);
+        List<Temperature> ret = mAidlWrapper.getCurrentTemperatures(true,
+                Temperature.TYPE_SOC);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperaturesWithType(
+                Temperature.TYPE_SOC);
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+
+        Mockito.when(mAidlHalMock.getTemperatures()).thenReturn(null);
+        ret = mAidlWrapper.getCurrentTemperatures(false, Temperature.TYPE_SOC);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatures();
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+    }
+
+    @Test
     public void getCurrentTemperatures_invalidStatus_aidl() throws RemoteException {
         android.hardware.thermal.Temperature halTInvalid =
                 new android.hardware.thermal.Temperature();
@@ -210,6 +255,55 @@
     }
 
     @Test
+    public void getCurrentCoolingDevices_aidl() throws RemoteException {
+        android.hardware.thermal.CoolingDevice halC1 = new android.hardware.thermal.CoolingDevice();
+        halC1.type = CoolingType.SPEAKER;
+        halC1.name = "test1";
+        halC1.value = 10;
+        android.hardware.thermal.CoolingDevice halC2 = new android.hardware.thermal.CoolingDevice();
+        halC2.type = CoolingType.MODEM;
+        halC2.name = "test2";
+        halC2.value = 110;
+
+        Mockito.when(mAidlHalMock.getCoolingDevicesWithType(Mockito.anyInt())).thenReturn(
+                new android.hardware.thermal.CoolingDevice[]{
+                        halC1, halC2
+                }
+        );
+        Mockito.when(mAidlHalMock.getCoolingDevices()).thenReturn(
+                new android.hardware.thermal.CoolingDevice[]{
+                        halC1, halC2
+                }
+        );
+        List<CoolingDevice> ret = mAidlWrapper.getCurrentCoolingDevices(false,
+                CoolingType.COMPONENT);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getCoolingDevices();
+
+        CoolingDevice expectedC1 = new CoolingDevice(halC1.value, halC1.type, halC1.name);
+        CoolingDevice expectedC2 = new CoolingDevice(halC2.value, halC2.type, halC2.name);
+        List<CoolingDevice> expectedRet = List.of(expectedC1, expectedC2);
+        assertTrue("Got cooling device list as " + ret + " with different values compared to "
+                + expectedRet, expectedRet.containsAll(ret));
+    }
+
+    @Test
+    public void getCurrentCoolingDevices_nullResult_aidl() throws RemoteException {
+        Mockito.when(mAidlHalMock.getCoolingDevicesWithType(Mockito.anyInt())).thenReturn(null);
+        List<CoolingDevice> ret = mAidlWrapper.getCurrentCoolingDevices(true,
+                CoolingType.COMPONENT);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getCoolingDevicesWithType(
+                CoolingType.COMPONENT);
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+
+        Mockito.when(mAidlHalMock.getCoolingDevices()).thenReturn(null);
+        ret = mAidlWrapper.getCurrentCoolingDevices(false, CoolingType.COMPONENT);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getCoolingDevices();
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+    }
+
+    @Test
     public void getCurrentCoolingDevices_withFilter_aidl() throws RemoteException {
         android.hardware.thermal.CoolingDevice halC1 = new android.hardware.thermal.CoolingDevice();
         halC1.type = CoolingType.SPEAKER;
@@ -292,6 +386,44 @@
     }
 
     @Test
+    public void getTemperatureThresholds_aidl() throws RemoteException {
+        TemperatureThreshold halT1 = new TemperatureThreshold();
+        halT1.name = "test1";
+        halT1.type = Temperature.TYPE_SKIN;
+        halT1.hotThrottlingThresholds = new float[]{1, 2, 3};
+        halT1.coldThrottlingThresholds = new float[]{};
+
+        TemperatureThreshold halT2 = new TemperatureThreshold();
+        halT2.name = "test2";
+        halT2.type = Temperature.TYPE_SOC;
+        halT2.hotThrottlingThresholds = new float[]{};
+        halT2.coldThrottlingThresholds = new float[]{3, 2, 1};
+
+        Mockito.when(mAidlHalMock.getTemperatureThresholds()).thenReturn(
+                new TemperatureThreshold[]{halT1, halT2}
+        );
+        List<TemperatureThreshold> ret = mAidlWrapper.getTemperatureThresholds(false,
+                Temperature.TYPE_UNKNOWN);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatureThresholds();
+
+        assertEquals("Got unexpected temperature thresholds size", 2, ret.size());
+        TemperatureThreshold threshold = ret.get(0).type == Temperature.TYPE_SKIN ? ret.get(0)
+                : ret.get(1);
+
+        assertEquals(halT1.name, threshold.name);
+        assertEquals(halT1.type, threshold.type);
+        assertArrayEquals(halT1.hotThrottlingThresholds, threshold.hotThrottlingThresholds, 0.1f);
+        assertArrayEquals(halT1.coldThrottlingThresholds, threshold.coldThrottlingThresholds, 0.1f);
+
+        threshold = ret.get(0).type == Temperature.TYPE_SOC ? ret.get(0) : ret.get(1);
+
+        assertEquals(halT2.name, threshold.name);
+        assertEquals(halT2.type, threshold.type);
+        assertArrayEquals(halT2.hotThrottlingThresholds, threshold.hotThrottlingThresholds, 0.1f);
+        assertArrayEquals(halT2.coldThrottlingThresholds, threshold.coldThrottlingThresholds, 0.1f);
+    }
+
+    @Test
     public void getTemperatureThresholds_withFilter_aidl() throws RemoteException {
         TemperatureThreshold halT1 = new TemperatureThreshold();
         halT1.name = "test1";
@@ -300,18 +432,18 @@
         halT1.coldThrottlingThresholds = new float[]{};
 
         TemperatureThreshold halT2 = new TemperatureThreshold();
-        halT1.name = "test2";
-        halT1.type = Temperature.TYPE_SOC;
-        halT1.hotThrottlingThresholds = new float[]{};
-        halT1.coldThrottlingThresholds = new float[]{3, 2, 1};
+        halT2.name = "test2";
+        halT2.type = Temperature.TYPE_SOC;
+        halT2.hotThrottlingThresholds = new float[]{};
+        halT2.coldThrottlingThresholds = new float[]{3, 2, 1};
 
         Mockito.when(mAidlHalMock.getTemperatureThresholdsWithType(Mockito.anyInt())).thenReturn(
                 new TemperatureThreshold[]{halT1, halT2}
         );
         List<TemperatureThreshold> ret = mAidlWrapper.getTemperatureThresholds(true,
-                Temperature.TYPE_SOC);
+                Temperature.TYPE_SKIN);
         Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatureThresholdsWithType(
-                Temperature.TYPE_SOC);
+                Temperature.TYPE_SKIN);
 
         assertEquals("Got unexpected temperature thresholds size", 1, ret.size());
         TemperatureThreshold threshold = ret.get(0);
@@ -322,6 +454,24 @@
     }
 
     @Test
+    public void getTemperatureThresholds_nullResult_aidl() throws RemoteException {
+        Mockito.when(mAidlHalMock.getTemperatureThresholdsWithType(Mockito.anyInt())).thenReturn(
+                null);
+        List<TemperatureThreshold> ret = mAidlWrapper.getTemperatureThresholds(true,
+                Temperature.TYPE_SOC);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatureThresholdsWithType(
+                Temperature.TYPE_SOC);
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+
+        Mockito.when(mAidlHalMock.getTemperatureThresholds()).thenReturn(null);
+        ret = mAidlWrapper.getTemperatureThresholds(false, Temperature.TYPE_SOC);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatureThresholds();
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+    }
+
+    @Test
     public void getTemperatureThresholds_illegalArgument_aidl() throws RemoteException {
         Mockito.when(mAidlHalMock.getTemperatureThresholdsWithType(Mockito.anyInt())).thenThrow(
                 new IllegalArgumentException());