Merge "Per-uid timeouts." into sc-dev
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 ed55f00..3bbc945 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -64,7 +64,7 @@
     public void onStart() {
         publishBinderService(Context.APP_SEARCH_SERVICE, new Stub());
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
-        mImplInstanceManager = new ImplInstanceManager(getContext());
+        mImplInstanceManager = ImplInstanceManager.getInstance(getContext());
     }
 
     private class Stub extends IAppSearchManager.Stub {
@@ -102,7 +102,8 @@
                     }
                     schemasPackageAccessible.put(entry.getKey(), packageIdentifiers);
                 }
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 impl.setSchema(
                         packageName,
                         databaseName,
@@ -133,7 +134,8 @@
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 verifyCallingPackage(callingUid, packageName);
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 List<AppSearchSchema> schemas = impl.getSchema(packageName, databaseName);
                 List<Bundle> schemaBundles = new ArrayList<>(schemas.size());
                 for (int i = 0; i < schemas.size(); i++) {
@@ -166,7 +168,8 @@
                 verifyCallingPackage(callingUid, packageName);
                 AppSearchBatchResult.Builder<String, Void> resultBuilder =
                         new AppSearchBatchResult.Builder<>();
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 for (int i = 0; i < documentBundles.size(); i++) {
                     GenericDocument document = new GenericDocument(documentBundles.get(i));
                     try {
@@ -207,12 +210,18 @@
                 verifyCallingPackage(callingUid, packageName);
                 AppSearchBatchResult.Builder<String, Bundle> resultBuilder =
                         new AppSearchBatchResult.Builder<>();
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 for (int i = 0; i < uris.size(); i++) {
                     String uri = uris.get(i);
                     try {
-                        GenericDocument document = impl.getDocument(packageName, databaseName,
-                                namespace, uri, typePropertyPaths);
+                        GenericDocument document =
+                                impl.getDocument(
+                                        packageName,
+                                        databaseName,
+                                        namespace,
+                                        uri,
+                                        typePropertyPaths);
                         resultBuilder.setSuccess(uri, document.getBundle());
                     } catch (Throwable t) {
                         resultBuilder.setResult(uri, throwableToFailedResult(t));
@@ -245,7 +254,8 @@
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 verifyCallingPackage(callingUid, packageName);
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 SearchResultPage searchResultPage =
                         impl.query(
                                 packageName,
@@ -278,12 +288,14 @@
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 verifyCallingPackage(callingUid, packageName);
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
-                SearchResultPage searchResultPage = impl.globalQuery(
-                        queryExpression,
-                        new SearchSpec(searchSpecBundle),
-                        packageName,
-                        callingUid);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
+                SearchResultPage searchResultPage =
+                        impl.globalQuery(
+                                queryExpression,
+                                new SearchSpec(searchSpecBundle),
+                                packageName,
+                                callingUid);
                 invokeCallbackOnResult(
                         callback,
                         AppSearchResult.newSuccessfulResult(searchResultPage.getBundle()));
@@ -306,7 +318,8 @@
             // TODO(b/162450968) check nextPageToken is being advanced by the same uid as originally
             // opened it
             try {
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 SearchResultPage searchResultPage = impl.getNextPage(nextPageToken);
                 invokeCallbackOnResult(
                         callback,
@@ -324,7 +337,8 @@
             int callingUserId = handleIncomingUser(userId, callingUid);
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 impl.invalidateNextPageToken(nextPageToken);
             } catch (Throwable t) {
                 Log.e(TAG, "Unable to invalidate the query page token", t);
@@ -350,15 +364,11 @@
             int callingUserId = handleIncomingUser(userId, callingUid);
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
-                impl.reportUsage(
-                        packageName,
-                        databaseName,
-                        namespace,
-                        uri,
-                        usageTimeMillis);
-                invokeCallbackOnResult(callback,
-                        AppSearchResult.newSuccessfulResult(/*result=*/ null));
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
+                impl.reportUsage(packageName, databaseName, namespace, uri, usageTimeMillis);
+                invokeCallbackOnResult(
+                        callback, AppSearchResult.newSuccessfulResult(/*result=*/ null));
             } catch (Throwable t) {
                 invokeCallbackOnError(callback, t);
             } finally {
@@ -385,7 +395,8 @@
                 verifyCallingPackage(callingUid, packageName);
                 AppSearchBatchResult.Builder<String, Void> resultBuilder =
                         new AppSearchBatchResult.Builder<>();
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 for (int i = 0; i < uris.size(); i++) {
                     String uri = uris.get(i);
                     try {
@@ -421,7 +432,8 @@
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 verifyCallingPackage(callingUid, packageName);
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 impl.removeByQuery(
                         packageName,
                         databaseName,
@@ -441,7 +453,8 @@
             int callingUserId = handleIncomingUser(userId, callingUid);
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 impl.persistToDisk();
             } catch (Throwable t) {
                 Log.e(TAG, "Unable to persist the data to disk", t);
@@ -457,7 +470,7 @@
             int callingUserId = handleIncomingUser(userId, callingUid);
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
-                mImplInstanceManager.getInstance(callingUserId);
+                mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
             } catch (Throwable t) {
                 invokeCallbackOnError(callback, t);
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
index fe3c2e1..97b1a8c 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
@@ -41,14 +41,33 @@
 public final class ImplInstanceManager {
     private static final String APP_SEARCH_DIR = "appSearch";
 
-    private static final SparseArray<AppSearchImpl> sInstances = new SparseArray<>();
+    private static ImplInstanceManager sImplInstanceManager;
 
-    private final Context mContext;
+    private final SparseArray<AppSearchImpl> mInstances = new SparseArray<>();
     private final String mGlobalQuerierPackage;
 
-    public ImplInstanceManager(@NonNull Context context) {
-        mContext = context;
-        mGlobalQuerierPackage = getGlobalAppSearchDataQuerierPackageName(mContext);
+    private ImplInstanceManager(@NonNull String globalQuerierPackage) {
+        mGlobalQuerierPackage = globalQuerierPackage;
+    }
+
+    /**
+     * Gets an instance of ImplInstanceManager to be used.
+     *
+     * <p>If no instance has been initialized yet, a new one will be created. Otherwise, the
+     * existing instance will be returned.
+     */
+    @NonNull
+    public static ImplInstanceManager getInstance(@NonNull Context context) {
+        if (sImplInstanceManager == null) {
+            synchronized (ImplInstanceManager.class) {
+                if (sImplInstanceManager == null) {
+                    sImplInstanceManager =
+                            new ImplInstanceManager(
+                                    getGlobalAppSearchDataQuerierPackageName(context));
+                }
+            }
+        }
+        return sImplInstanceManager;
     }
 
     /**
@@ -57,30 +76,30 @@
      * <p>If no AppSearchImpl instance exists for this user, Icing will be initialized and one will
      * be created.
      *
+     * @param context The context
      * @param userId The multi-user userId of the device user calling AppSearch
      * @return An initialized {@link AppSearchImpl} for this user
      */
     @NonNull
-    public AppSearchImpl getInstance(@UserIdInt int userId)
+    public AppSearchImpl getAppSearchImpl(@NonNull Context context, @UserIdInt int userId)
             throws AppSearchException {
-        AppSearchImpl instance = sInstances.get(userId);
+        AppSearchImpl instance = mInstances.get(userId);
         if (instance == null) {
             synchronized (ImplInstanceManager.class) {
-                instance = sInstances.get(userId);
+                instance = mInstances.get(userId);
                 if (instance == null) {
-                    instance = createImpl(userId);
-                    sInstances.put(userId, instance);
+                    instance = createImpl(context, userId);
+                    mInstances.put(userId, instance);
                 }
             }
         }
         return instance;
     }
 
-    private AppSearchImpl createImpl(@UserIdInt int userId)
+    private AppSearchImpl createImpl(@NonNull Context context, @UserIdInt int userId)
             throws AppSearchException {
-        File appSearchDir = getAppSearchDir(mContext, userId);
-        return AppSearchImpl.create(
-                appSearchDir, mContext, userId, mGlobalQuerierPackage);
+        File appSearchDir = getAppSearchDir(context, userId);
+        return AppSearchImpl.create(appSearchDir, context, userId, mGlobalQuerierPackage);
     }
 
     private static File getAppSearchDir(@NonNull Context context, @UserIdInt int userId) {
@@ -96,7 +115,8 @@
      *
      * @param context Context of the system service.
      */
-    private static String getGlobalAppSearchDataQuerierPackageName(Context context) {
+    @NonNull
+    private static String getGlobalAppSearchDataQuerierPackageName(@NonNull Context context) {
         String globalAppSearchDataQuerierPackage =
                 context.getString(R.string.config_globalAppSearchDataQuerierPackage);
         try {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 68d3a92..49f508d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5255,6 +5255,7 @@
                 // We still want a time to be set but gone, such that we can show and hide it
                 // on demand in case it's a child notification without anything in the header
                 contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime);
+                setTextViewColorSecondary(contentView, R.id.time, p);
             }
         }
 
diff --git a/core/java/android/app/people/ConversationChannel.aidl b/core/java/android/app/people/ConversationChannel.aidl
new file mode 100644
index 0000000..78df2f1
--- /dev/null
+++ b/core/java/android/app/people/ConversationChannel.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.people;
+
+parcelable ConversationChannel;
\ No newline at end of file
diff --git a/core/java/android/app/people/IPeopleManager.aidl b/core/java/android/app/people/IPeopleManager.aidl
index 0d12ed0..ebe9f60 100644
--- a/core/java/android/app/people/IPeopleManager.aidl
+++ b/core/java/android/app/people/IPeopleManager.aidl
@@ -17,6 +17,7 @@
 package android.app.people;
 
 import android.app.people.ConversationStatus;
+import android.app.people.ConversationChannel;
 import android.content.pm.ParceledListSlice;
 import android.net.Uri;
 import android.os.IBinder;
@@ -26,6 +27,13 @@
  * {@hide}
  */
 interface IPeopleManager {
+
+    /**
+    * Returns the specified conversation from the conversations list. If the conversation can't be
+    * found, returns null.
+    */
+    ConversationChannel getConversation(in String packageName, int userId, in String shortcutId);
+
     /**
      * Returns the recent conversations. The conversations that have customized notification
      * settings are excluded from the returned list.
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index b1b2925..8b6082b3 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -28,7 +28,10 @@
 import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
 import static android.app.AppOpsManager.opToPermission;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED;
+import static android.media.AudioSystem.MODE_IN_COMMUNICATION;
+import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.app.AppOpsManager;
 import android.content.ComponentName;
@@ -41,12 +44,14 @@
 import android.content.pm.ResolveInfo;
 import android.icu.text.ListFormatter;
 import android.location.LocationManager;
+import android.media.AudioManager;
 import android.os.Process;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.speech.RecognitionService;
 import android.speech.RecognizerIntent;
+import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.view.inputmethod.InputMethodInfo;
@@ -75,6 +80,9 @@
     private static final String PROPERTY_LOCATION_INDICATORS_ENABLED =
             "location_indicators_enabled";
 
+    /** Whether to show the Permissions Hub.  */
+    private static final String PROPERTY_PERMISSIONS_HUB_2_ENABLED = "permissions_hub_2_enabled";
+
     /** How long after an access to show it as "recent" */
     private static final String RECENT_ACCESS_TIME_MS = "recent_acccess_time_ms";
 
@@ -84,17 +92,25 @@
     /** The name of the expected voice IME subtype */
     private static final String VOICE_IME_SUBTYPE = "voice";
 
+    private static final String SYSTEM_PKG = "android";
+
     private static final long DEFAULT_RUNNING_TIME_MS = 5000L;
     private static final long DEFAULT_RECENT_TIME_MS = 30000L;
 
+    private static boolean shouldShowPermissionsHub() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+                PROPERTY_PERMISSIONS_HUB_2_ENABLED, false);
+    }
+
     private static boolean shouldShowIndicators() {
         return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
-                PROPERTY_CAMERA_MIC_ICONS_ENABLED, true);
+                PROPERTY_CAMERA_MIC_ICONS_ENABLED, true) || shouldShowPermissionsHub();
     }
 
     private static boolean shouldShowLocationIndicator() {
         return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
-                PROPERTY_LOCATION_INDICATORS_ENABLED, false);
+                PROPERTY_LOCATION_INDICATORS_ENABLED, false)
+                || shouldShowPermissionsHub();
     }
 
     private static long getRecentThreshold(Long now) {
@@ -113,7 +129,7 @@
     );
 
     private static final List<String> MIC_OPS = List.of(
-            OPSTR_PHONE_CALL_CAMERA,
+            OPSTR_PHONE_CALL_MICROPHONE,
             OPSTR_RECORD_AUDIO
     );
 
@@ -163,6 +179,13 @@
         return mUserContexts.get(user);
     }
 
+    // TODO ntmyren: Replace this with better check if this moves beyond teamfood
+    private boolean isAppPredictor(String packageName, UserHandle user) {
+        return shouldShowPermissionsHub() && getUserContext(user).getPackageManager()
+                .checkPermission(Manifest.permission.MANAGE_APP_PREDICTIONS, packageName)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
     /**
      * @see PermissionManager.getIndicatorAppOpUsageData
      */
@@ -186,7 +209,28 @@
         Map<PackageAttribution, CharSequence> packagesWithAttributionLabels =
                 getTrustedAttributions(rawUsages.get(MICROPHONE), proxyChains);
 
-        List<String> usedPermGroups = new ArrayList<>(rawUsages.keySet());
+        ArrayList<String> usedPermGroups = new ArrayList<>(rawUsages.keySet());
+
+        // If we have a phone call, and a carrier privileged app using microphone, hide the
+        // phone call.
+        AudioManager audioManager = mContext.getSystemService(AudioManager.class);
+        boolean hasPhoneCall = usedPermGroups.contains(OPSTR_PHONE_CALL_CAMERA)
+                || usedPermGroups.contains(OPSTR_PHONE_CALL_MICROPHONE);
+        if (hasPhoneCall && usedPermGroups.contains(MICROPHONE) && audioManager.getMode()
+                == MODE_IN_COMMUNICATION) {
+            TelephonyManager telephonyManager =
+                    mContext.getSystemService(TelephonyManager.class);
+            List<OpUsage> permUsages = rawUsages.get(MICROPHONE);
+            for (int usageNum = 0; usageNum < permUsages.size(); usageNum++) {
+                if (telephonyManager.checkCarrierPrivilegesForPackage(
+                        permUsages.get(usageNum).packageName)
+                        == CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+                    usedPermGroups.remove(OPSTR_PHONE_CALL_CAMERA);
+                    usedPermGroups.remove(OPSTR_PHONE_CALL_MICROPHONE);
+                }
+            }
+        }
+
         for (int permGroupNum = 0; permGroupNum < usedPermGroups.size(); permGroupNum++) {
             boolean isPhone = false;
             String permGroup = usedPermGroups.get(permGroupNum);
@@ -269,8 +313,11 @@
                     if (lastAccessTime < recentThreshold && !attrOpEntry.isRunning()) {
                         continue;
                     }
-                    if (!isUserSensitive(packageName, user, op)
-                            && !isLocationProvider(packageName, user)) {
+
+                    if (packageName.equals(SYSTEM_PKG)
+                            || (!isUserSensitive(packageName, user, op)
+                            && !isLocationProvider(packageName, user)
+                            && !isAppPredictor(packageName, user))) {
                         continue;
                     }
 
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 106e392..0a1a231 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -414,6 +414,15 @@
      */
     public static final int SECURE = 0x00000080;
 
+
+    /**
+     * Queue up BufferStateLayer buffers instead of dropping the oldest buffer when this flag is
+     * set. This blocks the client until all the buffers have been presented. If the buffers
+     * have presentation timestamps, then we may drop buffers.
+     * @hide
+     */
+    public static final int ENABLE_BACKPRESSURE = 0x00000100;
+
     /**
      * Surface creation flag: Creates a surface where color components are interpreted
      * as "non pre-multiplied" by their alpha channel. Of course this flag is
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 0e878fc..4ef63ae 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -262,7 +262,7 @@
         }
     }
 
-    binder::Status onScreenCaptureComplete(
+    binder::Status onScreenCaptureCompleted(
             const gui::ScreenCaptureResults& captureResults) override {
         JNIEnv* env = getenv();
         if (captureResults.result != NO_ERROR || captureResults.buffer == nullptr) {
@@ -270,6 +270,7 @@
                                 gScreenCaptureListenerClassInfo.onScreenCaptureComplete, nullptr);
             return binder::Status::ok();
         }
+        captureResults.fence->waitForever("");
         jobject jhardwareBuffer = android_hardware_HardwareBuffer_createFromAHardwareBuffer(
                 env, captureResults.buffer->toAHardwareBuffer());
         const jint namedColorSpace =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
index 4874d3c..a4cd3c5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
@@ -47,6 +47,11 @@
     }
 
     @Override
+    public void removeAllCallbacks() {
+        mHandler.removeCallbacksAndMessages(null);
+    }
+
+    @Override
     public void removeCallbacks(@NonNull Runnable r) {
         mHandler.removeCallbacks(r);
     }
@@ -55,9 +60,4 @@
     public boolean hasCallback(Runnable r) {
         return mHandler.hasCallbacks(r);
     }
-
-    @Override
-    public Looper getLooper() {
-        return mHandler.getLooper();
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
index 1149cce..b736fb0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
@@ -73,6 +73,11 @@
     void executeDelayed(Runnable runnable, long delayMillis);
 
     /**
+     * Removes all pending callbacks.
+     */
+    void removeAllCallbacks();
+
+    /**
      * See {@link android.os.Handler#removeCallbacks}.
      */
     void removeCallbacks(Runnable runnable);
@@ -81,9 +86,4 @@
      * See {@link android.os.Handler#hasCallbacks(Runnable)}.
      */
     boolean hasCallback(Runnable runnable);
-
-    /**
-     * Returns the looper that this executor is running on.
-     */
-    Looper getLooper();
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
index a74f476..37a91d0c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
@@ -56,7 +56,7 @@
     private final float[] mColor;
     private final float mAlpha;
     private final Rect mRect;
-    private final Handler mHandler;
+    private final Executor mMainExecutor;
     private final Point mDisplaySize = new Point();
     private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
@@ -76,13 +76,13 @@
                 @Override
                 public void onOneHandedAnimationStart(
                         OneHandedAnimationController.OneHandedTransitionAnimator animator) {
-                    mHandler.post(() -> showBackgroundPanelLayer());
+                    mMainExecutor.execute(() -> showBackgroundPanelLayer());
                 }
             };
 
     @Override
     public void onStopFinished(Rect bounds) {
-        mHandler.post(() -> removeBackgroundPanelLayer());
+        mMainExecutor.execute(() -> removeBackgroundPanelLayer());
     }
 
     public OneHandedBackgroundPanelOrganizer(Context context, DisplayController displayController,
@@ -94,7 +94,7 @@
         mColor = new float[]{defaultRGB, defaultRGB, defaultRGB};
         mAlpha = res.getFloat(R.dimen.config_one_handed_background_alpha);
         mRect = new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
-        mHandler = new Handler();
+        mMainExecutor = executor;
         mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
index 1ed121f..49b7e05 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
@@ -221,8 +221,14 @@
                     displaySize.y);
             mInputMonitor = InputManager.getInstance().monitorGestureInput(
                     "onehanded-gesture-offset", DEFAULT_DISPLAY);
-            mInputEventReceiver = new EventReceiver(
-                    mInputMonitor.getInputChannel(), mMainExecutor.getLooper());
+            try {
+                mMainExecutor.executeBlocking(() -> {
+                    mInputEventReceiver = new EventReceiver(
+                            mInputMonitor.getInputChannel(), Looper.myLooper());
+                });
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to create input event receiver", e);
+            }
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
index 60709be..c7a49ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
@@ -132,8 +132,14 @@
         if (mIsEnabled) {
             mInputMonitor = InputManager.getInstance().monitorGestureInput(
                     "onehanded-touch", DEFAULT_DISPLAY);
-            mInputEventReceiver = new EventReceiver(
-                    mInputMonitor.getInputChannel(), mMainExecutor.getLooper());
+            try {
+                mMainExecutor.executeBlocking(() -> {
+                    mInputEventReceiver = new EventReceiver(
+                            mInputMonitor.getInputChannel(), Looper.myLooper());
+                });
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to create input event receiver", e);
+            }
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
index 7a634c3..6e3a20d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
@@ -147,7 +147,7 @@
             // Choreographer.getSfInstance() must be called on the thread that the input event
             // receiver should be receiving events
             mInputEventReceiver = new InputEventReceiver(inputChannel,
-                mMainExecutor.getLooper(), Choreographer.getSfInstance());
+                Looper.myLooper(), Choreographer.getSfInstance());
             if (mRegistrationListener != null) {
                 mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 41cc59d..8fb358a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -212,8 +212,14 @@
             // Register input event receiver
             mInputMonitor = InputManager.getInstance().monitorGestureInput(
                     "pip-resize", mDisplayId);
-            mInputEventReceiver = new PipResizeInputEventReceiver(
-                    mInputMonitor.getInputChannel(), mMainExecutor.getLooper());
+            try {
+                mMainExecutor.executeBlocking(() -> {
+                    mInputEventReceiver = new PipResizeInputEventReceiver(
+                            mInputMonitor.getInputChannel(), Looper.myLooper());
+                });
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to create input event receiver", e);
+            }
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index c9f5ae2..2b8b53c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -95,7 +95,6 @@
     private int mDeferResizeToNormalBoundsUntilRotation = -1;
     private int mDisplayRotation;
 
-    private final Handler mHandler = new Handler();
     private final PipAccessibilityInteractionConnection mConnection;
 
     // Behaviour states
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
index 5f5c30b..bf84a6e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
@@ -40,6 +40,11 @@
     }
 
     @Override
+    public void removeAllCallbacks() {
+        mRunnables.clear();
+    }
+
+    @Override
     public void removeCallbacks(Runnable r) {
         mRunnables.remove(r);
     }
@@ -49,11 +54,6 @@
         return mRunnables.contains(r);
     }
 
-    @Override
-    public Looper getLooper() {
-        return null;
-    }
-
     public void flushAll() {
         for (Runnable r : mRunnables) {
             r.run();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java
index 9219f15..bbe8891 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java
@@ -33,6 +33,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.ShellExecutor;
 
 import org.junit.Before;
@@ -48,7 +49,7 @@
 @RunWith(AndroidTestingRunner.class)
 public class OneHandedTimeoutHandlerTest extends OneHandedTestCase {
     private OneHandedTimeoutHandler mTimeoutHandler;
-    private ShellExecutor mMainExecutor;
+    private TestShellExecutor mMainExecutor;
 
     @Before
     public void setUp() throws Exception {
@@ -104,34 +105,4 @@
         mTimeoutHandler.resetTimer();
         assertTrue(mTimeoutHandler.hasScheduledTimeout());
     }
-
-    private class TestShellExecutor implements ShellExecutor {
-        private ArrayList<Runnable> mExecuted = new ArrayList<>();
-        private ArrayList<Runnable> mDelayed = new ArrayList<>();
-
-        @Override
-        public void execute(Runnable runnable) {
-            mExecuted.add(runnable);
-        }
-
-        @Override
-        public void executeDelayed(Runnable r, long delayMillis) {
-            mDelayed.add(r);
-        }
-
-        @Override
-        public void removeCallbacks(Runnable r) {
-            mDelayed.remove(r);
-        }
-
-        @Override
-        public boolean hasCallback(Runnable r) {
-            return mDelayed.contains(r);
-        }
-
-        @Override
-        public Looper getLooper() {
-            return Looper.myLooper();
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
index 93a8df4..cd3d6a8 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
@@ -26,8 +26,7 @@
     private String mPackageName;
     private long mTimeStarted;
     private StringBuilder mState;
-    // This is only used for items with mCode == AppOpsManager.OP_RECORD_AUDIO
-    private boolean mSilenced;
+    private boolean mIsDisabled;
 
     public AppOpItem(int code, int uid, String packageName, long timeStarted) {
         this.mCode = code;
@@ -58,16 +57,16 @@
         return mTimeStarted;
     }
 
-    public void setSilenced(boolean silenced) {
-        mSilenced = silenced;
+    public void setDisabled(boolean misDisabled) {
+        this.mIsDisabled = misDisabled;
     }
 
-    public boolean isSilenced() {
-        return mSilenced;
+    public boolean isDisabled() {
+        return mIsDisabled;
     }
 
     @Override
     public String toString() {
-        return mState.append(mSilenced).append(")").toString();
+        return mState.append(mIsDisabled).append(")").toString();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 1036c99..d8ca639 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.appops;
 
+import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA;
+import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE;
 import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED;
 
 import android.Manifest;
@@ -45,6 +47,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.util.Assert;
 
 import java.io.FileDescriptor;
@@ -64,7 +67,8 @@
 @SysUISingleton
 public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsController,
         AppOpsManager.OnOpActiveChangedInternalListener,
-        AppOpsManager.OnOpNotedListener, Dumpable {
+        AppOpsManager.OnOpNotedListener, IndividualSensorPrivacyController.Callback,
+        Dumpable {
 
     // This is the minimum time that we will keep AppOps that are noted on record. If multiple
     // occurrences of the same (op, package, uid) happen in a shorter interval, they will not be
@@ -77,8 +81,8 @@
     private final AppOpsManager mAppOps;
     private final AudioManager mAudioManager;
     private final LocationManager mLocationManager;
-    // TODO ntmyren: remove t
     private final PackageManager mPackageManager;
+    private final IndividualSensorPrivacyController mSensorPrivacyController;
 
     // mLocationProviderPackages are cached and updated only occasionally
     private static final long LOCATION_PROVIDER_UPDATE_FREQUENCY_MS = 30000;
@@ -91,6 +95,7 @@
     private final PermissionFlagsCache mFlagsCache;
     private boolean mListening;
     private boolean mMicMuted;
+    private boolean mCameraDisabled;
 
     @GuardedBy("mActiveItems")
     private final List<AppOpItem> mActiveItems = new ArrayList<>();
@@ -118,6 +123,7 @@
             DumpManager dumpManager,
             PermissionFlagsCache cache,
             AudioManager audioManager,
+            IndividualSensorPrivacyController sensorPrivacyController,
             BroadcastDispatcher dispatcher
     ) {
         mDispatcher = dispatcher;
@@ -129,7 +135,10 @@
             mCallbacksByCode.put(OPS[i], new ArraySet<>());
         }
         mAudioManager = audioManager;
-        mMicMuted = audioManager.isMicrophoneMute();
+        mSensorPrivacyController = sensorPrivacyController;
+        mMicMuted = audioManager.isMicrophoneMute()
+                || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE);
+        mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA);
         mLocationManager = context.getSystemService(LocationManager.class);
         mPackageManager = context.getPackageManager();
         dumpManager.registerDumpable(TAG, this);
@@ -147,6 +156,12 @@
             mAppOps.startWatchingActive(OPS, this);
             mAppOps.startWatchingNoted(OPS, this);
             mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler);
+            mSensorPrivacyController.addCallback(this);
+
+            mMicMuted = mAudioManager.isMicrophoneMute()
+                    || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE);
+            mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA);
+
             mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged(
                     mAudioManager.getActiveRecordingConfigurations()));
             mDispatcher.registerReceiverWithHandler(this,
@@ -156,6 +171,7 @@
             mAppOps.stopWatchingActive(this);
             mAppOps.stopWatchingNoted(this);
             mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback);
+            mSensorPrivacyController.removeCallback(this);
 
             mBGHandler.removeCallbacksAndMessages(null); // null removes all
             mDispatcher.unregisterReceiver(this);
@@ -235,11 +251,13 @@
             if (item == null && active) {
                 item = new AppOpItem(code, uid, packageName, System.currentTimeMillis());
                 if (code == AppOpsManager.OP_RECORD_AUDIO) {
-                    item.setSilenced(isAnyRecordingPausedLocked(uid));
+                    item.setDisabled(isAnyRecordingPausedLocked(uid));
+                } else if (code == AppOpsManager.OP_CAMERA) {
+                    item.setDisabled(mCameraDisabled);
                 }
                 mActiveItems.add(item);
                 if (DEBUG) Log.w(TAG, "Added item: " + item.toString());
-                return !item.isSilenced();
+                return !item.isDisabled();
             } else if (item != null && !active) {
                 mActiveItems.remove(item);
                 if (DEBUG) Log.w(TAG, "Removed item: " + item.toString());
@@ -409,7 +427,7 @@
                 AppOpItem item = mActiveItems.get(i);
                 if ((userId == UserHandle.USER_ALL
                         || UserHandle.getUserId(item.getUid()) == userId)
-                        && isUserVisible(item) && !item.isSilenced()) {
+                        && isUserVisible(item) && !item.isDisabled()) {
                     list.add(item);
                 }
             }
@@ -512,22 +530,27 @@
         return false;
     }
 
-    private void updateRecordingPausedStatus() {
+    private void updateSensorDisabledStatus() {
         synchronized (mActiveItems) {
             int size = mActiveItems.size();
             for (int i = 0; i < size; i++) {
                 AppOpItem item = mActiveItems.get(i);
+
+                boolean paused = false;
                 if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) {
-                    boolean paused = isAnyRecordingPausedLocked(item.getUid());
-                    if (item.isSilenced() != paused) {
-                        item.setSilenced(paused);
-                        notifySuscribers(
-                                item.getCode(),
-                                item.getUid(),
-                                item.getPackageName(),
-                                !item.isSilenced()
-                        );
-                    }
+                    paused = isAnyRecordingPausedLocked(item.getUid());
+                } else if (item.getCode() == AppOpsManager.OP_CAMERA) {
+                    paused = mCameraDisabled;
+                }
+
+                if (item.isDisabled() != paused) {
+                    item.setDisabled(paused);
+                    notifySuscribers(
+                            item.getCode(),
+                            item.getUid(),
+                            item.getPackageName(),
+                            !item.isDisabled()
+                    );
                 }
             }
         }
@@ -552,14 +575,27 @@
                     recordings.add(recording);
                 }
             }
-            updateRecordingPausedStatus();
+            updateSensorDisabledStatus();
         }
     };
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        mMicMuted = mAudioManager.isMicrophoneMute();
-        updateRecordingPausedStatus();
+        mMicMuted = mAudioManager.isMicrophoneMute()
+                || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE);
+        updateSensorDisabledStatus();
+    }
+
+    @Override
+    public void onSensorBlockedChanged(int sensor, boolean blocked) {
+        mBGHandler.post(() -> {
+            if (sensor == INDIVIDUAL_SENSOR_CAMERA) {
+                mCameraDisabled = blocked;
+            } else if (sensor == INDIVIDUAL_SENSOR_MICROPHONE) {
+                mMicMuted = mAudioManager.isMicrophoneMute() || blocked;
+            }
+            updateSensorDisabledStatus();
+        });
     }
 
     protected class H extends Handler {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index c2c6790..9be3566 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -23,7 +23,6 @@
 import android.net.Uri;
 import android.os.UserHandle;
 import android.util.Log;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewTreeObserver.InternalInsetsInfo;
 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
@@ -59,7 +58,6 @@
     private final Executor mBgExecutor;
     private final ImageExporter mImageExporter;
     private final ImageTileSet mImageTileSet;
-    private final LayoutInflater mLayoutInflater;
 
     private ZonedDateTime mCaptureTime;
     private UUID mRequestId;
@@ -81,7 +79,6 @@
         mBgExecutor = bgExecutor;
         mImageExporter = exporter;
         mImageTileSet = new ImageTileSet();
-        mLayoutInflater = mContext.getSystemService(LayoutInflater.class);
     }
 
     /**
@@ -114,7 +111,7 @@
         mEdit.setOnClickListener(this::onClicked);
         mShare.setOnClickListener(this::onClicked);
 
-        mPreview.setImageDrawable(mImageTileSet.getDrawable());
+        //mPreview.setImageDrawable(mImageTileSet.getDrawable());
         mConnection.start(this::startCapture);
     }
 
@@ -242,6 +239,7 @@
         if (mImageTileSet.isEmpty()) {
             session.end(mCallback::onFinish);
         } else {
+            mPreview.setImageDrawable(mImageTileSet.getDrawable());
             mExportFuture = mImageExporter.export(
                     mBgExecutor, mRequestId, mImageTileSet.toBitmap(), mCaptureTime);
             // The user chose an action already, link it to the result
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
index 231fe08..32d15ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
@@ -16,8 +16,8 @@
 
 package com.android.systemui.statusbar.policy;
 
-import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA;
-import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE;
+import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA;
+import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE;
 
 import android.hardware.SensorPrivacyManager;
 import android.hardware.SensorPrivacyManager.IndividualSensor;
@@ -30,7 +30,8 @@
 
 public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController {
 
-    private static final int[] SENSORS = new int[] {CAMERA, MICROPHONE};
+    private static final int[] SENSORS = new int[] {INDIVIDUAL_SENSOR_CAMERA,
+            INDIVIDUAL_SENSOR_MICROPHONE};
 
     private final @NonNull SensorPrivacyManager mSensorPrivacyManager;
     private final SparseBooleanArray mState = new SparseBooleanArray();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index 02143a7..bc322f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.appops;
 
+import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA;
+import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE;
+
 import static junit.framework.TestCase.assertFalse;
 
 import static org.junit.Assert.assertEquals;
@@ -49,6 +52,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -81,6 +85,8 @@
     private PermissionFlagsCache mFlagsCache;
     @Mock
     private PackageManager mPackageManager;
+    @Mock
+    private IndividualSensorPrivacyController mSensorPrivacyController;
     @Mock(stubOnly = true)
     private AudioManager mAudioManager;
     @Mock()
@@ -118,12 +124,18 @@
         when(mAudioManager.getActiveRecordingConfigurations())
                 .thenReturn(List.of(mPausedMockRecording));
 
+        when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA))
+                .thenReturn(false);
+        when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA))
+                .thenReturn(false);
+
         mController = new AppOpsControllerImpl(
                 mContext,
                 mTestableLooper.getLooper(),
                 mDumpManager,
                 mFlagsCache,
                 mAudioManager,
+                mSensorPrivacyController,
                 mDispatcher
         );
     }
@@ -133,6 +145,7 @@
         mController.setListening(true);
         verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsControllerImpl.OPS, mController);
         verify(mDispatcher, times(1)).registerReceiverWithHandler(eq(mController), any(), any());
+        verify(mSensorPrivacyController, times(1)).addCallback(mController);
     }
 
     @Test
@@ -140,6 +153,7 @@
         mController.setListening(false);
         verify(mAppOpsManager, times(1)).stopWatchingActive(mController);
         verify(mDispatcher, times(1)).unregisterReceiver(mController);
+        verify(mSensorPrivacyController, times(1)).removeCallback(mController);
     }
 
     @Test
@@ -476,6 +490,71 @@
                 AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, false);
     }
 
+    @Test
+    public void testAudioFilteredWhenMicDisabled() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA},
+                mCallback);
+        mTestableLooper.processAllMessages();
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+        List<AppOpItem> list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode());
+        assertFalse(list.get(0).isDisabled());
+
+        // Add a camera op, and disable the microphone. The camera op should be the only op returned
+        mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_MICROPHONE, true);
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+        list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
+
+
+        // Re enable the microphone, and verify the op returns
+        mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_MICROPHONE, false);
+        mTestableLooper.processAllMessages();
+
+        list = mController.getActiveAppOps();
+        assertEquals(2, list.size());
+        int micIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 1 : 0;
+        assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(micIdx).getCode());
+    }
+
+    @Test
+    public void testCameraFilteredWhenCameraDisabled() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA},
+                mCallback);
+        mTestableLooper.processAllMessages();
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+        List<AppOpItem> list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
+        assertFalse(list.get(0).isDisabled());
+
+        // Add an audio op, and disable the camera. The audio op should be the only op returned
+        mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_CAMERA, true);
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+        list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode());
+
+        // Re enable the camera, and verify the op returns
+        mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_CAMERA, false);
+        mTestableLooper.processAllMessages();
+
+        list = mController.getActiveAppOps();
+        assertEquals(2, list.size());
+        int cameraIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 0 : 1;
+        assertEquals(AppOpsManager.OP_CAMERA, list.get(cameraIdx).getCode());
+    }
+
     private class TestHandler extends AppOpsControllerImpl.H {
         TestHandler(Looper looper) {
             mController.super(looper);
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index b5b93d6..142f64f 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -74,7 +74,6 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
@@ -1297,10 +1296,7 @@
                 return null;
             }
 
-            long currentNanos = SystemClock.elapsedRealtimeNanos();
-            long deltaMs = NANOSECONDS.toMillis(
-                    location.getElapsedRealtimeAgeNanos(currentNanos));
-            return new LocationTime(location.getTime() + deltaMs, currentNanos);
+            return new LocationTime(location.getTime(), location.getElapsedRealtimeNanos());
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b8d3e54..d2fc5b4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2153,6 +2153,10 @@
         void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
                 boolean requireFullPermission, boolean checkShell,
                 boolean requirePermissionWhenSameUser, String message);
+        SigningDetails getSigningDetails(@NonNull String packageName);
+        SigningDetails getSigningDetails(int uid);
+        boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId);
+        boolean filterAppAccess(String packageName, int callingUid, int userId);
     }
 
     /**
@@ -4578,6 +4582,40 @@
             throw new SecurityException(errorMessage);
         }
 
+        public SigningDetails getSigningDetails(@NonNull String packageName) {
+            AndroidPackage p = mPackages.get(packageName);
+            if (p == null) {
+                return null;
+            }
+            return p.getSigningDetails();
+        }
+
+        public SigningDetails getSigningDetails(int uid) {
+            final int appId = UserHandle.getAppId(uid);
+            final Object obj = mSettings.getSettingLPr(appId);
+            if (obj != null) {
+                if (obj instanceof SharedUserSetting) {
+                    return ((SharedUserSetting) obj).signatures.mSigningDetails;
+                } else if (obj instanceof PackageSetting) {
+                    final PackageSetting ps = (PackageSetting) obj;
+                    return ps.signatures.mSigningDetails;
+                }
+            }
+            return SigningDetails.UNKNOWN;
+        }
+
+        public boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId) {
+            PackageSetting ps = getPackageSetting(pkg.getPackageName());
+            return shouldFilterApplicationLocked(ps, callingUid,
+                    userId);
+        }
+
+        public boolean filterAppAccess(String packageName, int callingUid, int userId) {
+            PackageSetting ps = getPackageSetting(packageName);
+            return shouldFilterApplicationLocked(ps, callingUid,
+                    userId);
+        }
+
     }
 
     /**
@@ -4728,6 +4766,26 @@
                 return super.getPackageUidInternal(packageName, flags, userId, callingUid);
             }
         }
+        public SigningDetails getSigningDetails(@NonNull String packageName) {
+            synchronized (mLock) {
+                return super.getSigningDetails(packageName);
+            }
+        }
+        public SigningDetails getSigningDetails(int uid) {
+            synchronized (mLock) {
+                return super.getSigningDetails(uid);
+            }
+        }
+        public boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId) {
+            synchronized (mLock) {
+                return super.filterAppAccess(pkg, callingUid, userId);
+            }
+        }
+        public boolean filterAppAccess(String packageName, int callingUid, int userId) {
+            synchronized (mLock) {
+                return super.filterAppAccess(packageName, callingUid, userId);
+            }
+        }
     }
 
 
@@ -26560,6 +26618,22 @@
         return snapshotComputer().getPackage(uid);
     }
 
+    private SigningDetails getSigningDetails(@NonNull String packageName) {
+        return snapshotComputer().getSigningDetails(packageName);
+    }
+
+    private SigningDetails getSigningDetails(int uid) {
+        return snapshotComputer().getSigningDetails(uid);
+    }
+
+    private boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId) {
+        return snapshotComputer().filterAppAccess(pkg, callingUid, userId);
+    }
+
+    private boolean filterAppAccess(String packageName, int callingUid, int userId) {
+        return snapshotComputer().filterAppAccess(packageName, callingUid, userId);
+    }
+
     private class PackageManagerInternalImpl extends PackageManagerInternal {
         @Override
         public List<ApplicationInfo> getInstalledApplications(int flags, int userId,
@@ -26615,29 +26689,11 @@
         }
 
         private SigningDetails getSigningDetails(@NonNull String packageName) {
-            synchronized (mLock) {
-                AndroidPackage p = mPackages.get(packageName);
-                if (p == null) {
-                    return null;
-                }
-                return p.getSigningDetails();
-            }
+            return PackageManagerService.this.getSigningDetails(packageName);
         }
 
         private SigningDetails getSigningDetails(int uid) {
-            synchronized (mLock) {
-                final int appId = UserHandle.getAppId(uid);
-                final Object obj = mSettings.getSettingLPr(appId);
-                if (obj != null) {
-                    if (obj instanceof SharedUserSetting) {
-                        return ((SharedUserSetting) obj).signatures.mSigningDetails;
-                    } else if (obj instanceof PackageSetting) {
-                        final PackageSetting ps = (PackageSetting) obj;
-                        return ps.signatures.mSigningDetails;
-                    }
-                }
-                return SigningDetails.UNKNOWN;
-            }
+            return PackageManagerService.this.getSigningDetails(uid);
         }
 
         @Override
@@ -26652,20 +26708,12 @@
 
         @Override
         public boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId) {
-            synchronized (mLock) {
-                PackageSetting ps = getPackageSetting(pkg.getPackageName());
-                return PackageManagerService.this.shouldFilterApplicationLocked(ps, callingUid,
-                        userId);
-            }
+            return PackageManagerService.this.filterAppAccess(pkg, callingUid, userId);
         }
 
         @Override
         public boolean filterAppAccess(String packageName, int callingUid, int userId) {
-            synchronized (mLock) {
-                PackageSetting ps = getPackageSetting(packageName);
-                return PackageManagerService.this.shouldFilterApplicationLocked(ps, callingUid,
-                        userId);
-            }
+            return PackageManagerService.this.filterAppAccess(packageName, callingUid, userId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 73bcf47..0aaa1a1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4107,8 +4107,15 @@
      * Callbacks when the given type of {@link WindowContainer} animation finished running in the
      * hierarchy.
      */
-    void onWindowAnimationFinished(int type) {
+    void onWindowAnimationFinished(@NonNull WindowContainer wc, int type) {
         if (type == ANIMATION_TYPE_APP_TRANSITION || type == ANIMATION_TYPE_RECENTS) {
+            // Unfreeze the insets state of the frozen target when the animation finished if exists.
+            final Task task = wc.asTask();
+            if (task != null) {
+                task.forAllWindows(w -> {
+                    w.clearFrozenInsetsState();
+                }, true /* traverseTopToBottom */);
+            }
             removeImeSurfaceImmediately();
         }
     }
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 398049f..267f677 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -74,7 +74,7 @@
     private final ArraySet<InsetsControlTarget> mPendingControlChanged = new ArraySet<>();
 
     private final Consumer<WindowState> mDispatchInsetsChanged = w -> {
-        if (w.isVisible()) {
+        if (w.isReadyToDispatchInsetsState()) {
             w.notifyInsetsChanged();
         }
     };
@@ -117,7 +117,8 @@
         final @InternalInsetsType int type = provider != null
                 ? provider.getSource().getType() : ITYPE_INVALID;
         return getInsetsForTarget(type, target.getWindowingMode(), target.isAlwaysOnTop(),
-                isAboveIme(target));
+                isAboveIme(target),
+                target.getFrozenInsetsState() != null ? target.getFrozenInsetsState() : mState);
     }
 
     InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) {
@@ -132,7 +133,7 @@
         final @WindowingMode int windowingMode = token != null
                 ? token.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
         final boolean alwaysOnTop = token != null && token.isAlwaysOnTop();
-        return getInsetsForTarget(type, windowingMode, alwaysOnTop, isAboveIme(token));
+        return getInsetsForTarget(type, windowingMode, alwaysOnTop, isAboveIme(token), mState);
     }
 
     private boolean isAboveIme(WindowContainer target) {
@@ -180,9 +181,8 @@
      * @see #getInsetsForWindowMetrics
      */
     private InsetsState getInsetsForTarget(@InternalInsetsType int type,
-            @WindowingMode int windowingMode, boolean isAlwaysOnTop, boolean aboveIme) {
-        InsetsState state = mState;
-
+            @WindowingMode int windowingMode, boolean isAlwaysOnTop, boolean aboveIme,
+            @NonNull InsetsState state) {
         if (type != ITYPE_INVALID) {
             state = new InsetsState(state);
             state.removeSource(type);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 03fca11..dd4ee877 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2684,6 +2684,14 @@
             @Nullable ArrayList<WindowContainer> sources) {
         final Task task = asTask();
         if (task != null && !enter && !task.isHomeOrRecentsRootTask()) {
+            if (AppTransition.isClosingTransitOld(transit)) {
+                // Freezes the insets state when the window is in app exiting transition, to
+                // ensure the exiting window won't receive unexpected insets changes from the
+                // next window.
+                task.forAllWindows(w -> {
+                    w.freezeInsetsState();
+                }, true /* traverseTopToBottom */);
+            }
             mDisplayContent.showImeScreenshot();
         }
         final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
@@ -2831,7 +2839,7 @@
         }
         mSurfaceAnimationSources.clear();
         if (mDisplayContent != null) {
-            mDisplayContent.onWindowAnimationFinished(type);
+            mDisplayContent.onWindowAnimationFinished(this, type);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 9f3188b..9a7823e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -713,6 +713,12 @@
     private @Nullable InsetsSourceProvider mControllableInsetProvider;
     private final InsetsState mRequestedInsetsState = new InsetsState();
 
+    /**
+     * Freeze the insets state in some cases that not necessarily keeps up-to-date to the client.
+     * (e.g app exiting transition)
+     */
+    private InsetsState mFrozenInsetsState;
+
     @Nullable InsetsSourceProvider mPendingPositionChanged;
 
     private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
@@ -758,6 +764,33 @@
         }
     }
 
+    /**
+     * Set a freeze state for the window to ignore dispatching its insets state to the client.
+     *
+     * Used to keep the insets state for some use cases. (e.g. app exiting transition)
+     */
+    void freezeInsetsState() {
+        if (mFrozenInsetsState == null) {
+            mFrozenInsetsState = new InsetsState(getInsetsState(), true /* copySources */);
+        }
+    }
+
+    void clearFrozenInsetsState() {
+        mFrozenInsetsState = null;
+    }
+
+    InsetsState getFrozenInsetsState() {
+        return mFrozenInsetsState;
+    }
+
+    /**
+     * Check if the insets state of the window is ready to dispatch to the client when invoking
+     * {@link InsetsStateController#notifyInsetsChanged}.
+     */
+    boolean isReadyToDispatchInsetsState() {
+        return isVisible() && mFrozenInsetsState == null;
+    }
+
     void seamlesslyRotateIfAllowed(Transaction transaction, @Rotation int oldRotation,
             @Rotation int rotation, boolean requested) {
         // Invisible windows and the wallpaper do not participate in the seamless rotation animation
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index 091e688..5453de1 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -45,7 +45,6 @@
 import com.android.server.SystemService;
 import com.android.server.people.data.DataManager;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
@@ -156,6 +155,13 @@
     final IBinder mService = new IPeopleManager.Stub() {
 
         @Override
+        public ConversationChannel getConversation(
+                String packageName, int userId, String shortcutId) {
+            enforceSystemRootOrSystemUI(getContext(), "get conversation");
+            return mDataManager.getConversation(packageName, userId, shortcutId);
+        }
+
+        @Override
         public ParceledListSlice<ConversationChannel> getRecentConversations() {
             enforceSystemRootOrSystemUI(getContext(), "get recent conversations");
             return new ParceledListSlice<>(
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 7521415..9a9a171 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -222,33 +222,65 @@
                 mContext.getPackageName(), intentFilter, callingUserId);
     }
 
+    /**
+     * Returns a {@link ConversationChannel} with the associated {@code shortcutId} if existent.
+     * Otherwise, returns null.
+     */
+    @Nullable
+    public ConversationChannel getConversation(String packageName, int userId, String shortcutId) {
+        UserData userData = getUnlockedUserData(userId);
+        if (userData != null) {
+            PackageData packageData = userData.getPackageData(packageName);
+            // App may have been uninstalled.
+            if (packageData != null) {
+                return getConversationChannel(packageData, shortcutId);
+            }
+        }
+        return null;
+    }
+
+    @Nullable
+    private ConversationChannel getConversationChannel(PackageData packageData, String shortcutId) {
+        ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId);
+        if (conversationInfo == null) {
+            return null;
+        }
+        int userId = packageData.getUserId();
+        String packageName = packageData.getPackageName();
+        ShortcutInfo shortcutInfo = getShortcut(packageName, userId, shortcutId);
+        if (shortcutInfo == null) {
+            return null;
+        }
+        int uid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
+        NotificationChannel parentChannel =
+                mNotificationManagerInternal.getNotificationChannel(packageName, uid,
+                        conversationInfo.getParentNotificationChannelId());
+        NotificationChannelGroup parentChannelGroup = null;
+        if (parentChannel != null) {
+            parentChannelGroup =
+                    mNotificationManagerInternal.getNotificationChannelGroup(packageName,
+                            uid, parentChannel.getId());
+        }
+        return new ConversationChannel(shortcutInfo, uid, parentChannel,
+                parentChannelGroup,
+                conversationInfo.getLastEventTimestamp(),
+                hasActiveNotifications(packageName, userId, shortcutId));
+    }
+
     /** Returns the cached non-customized recent conversations. */
     public List<ConversationChannel> getRecentConversations(@UserIdInt int callingUserId) {
         List<ConversationChannel> conversationChannels = new ArrayList<>();
         forPackagesInProfile(callingUserId, packageData -> {
-            String packageName = packageData.getPackageName();
-            int userId = packageData.getUserId();
             packageData.forAllConversations(conversationInfo -> {
                 if (!isCachedRecentConversation(conversationInfo)) {
                     return;
                 }
                 String shortcutId = conversationInfo.getShortcutId();
-                ShortcutInfo shortcutInfo = getShortcut(packageName, userId, shortcutId);
-                int uid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
-                NotificationChannel parentChannel =
-                        mNotificationManagerInternal.getNotificationChannel(packageName, uid,
-                                conversationInfo.getParentNotificationChannelId());
-                if (shortcutInfo == null || parentChannel == null) {
+                ConversationChannel channel = getConversationChannel(packageData, shortcutId);
+                if (channel == null || channel.getParentNotificationChannel() == null) {
                     return;
                 }
-                NotificationChannelGroup parentChannelGroup =
-                        mNotificationManagerInternal.getNotificationChannelGroup(packageName,
-                                uid, parentChannel.getId());
-                conversationChannels.add(
-                        new ConversationChannel(shortcutInfo, uid, parentChannel,
-                                parentChannelGroup,
-                                conversationInfo.getLastEventTimestamp(),
-                                hasActiveNotifications(packageName, userId, shortcutId)));
+                conversationChannels.add(channel);
             });
         });
         return conversationChannels;
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 63330d5..161d316 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -515,6 +515,85 @@
     }
 
     @Test
+    public void testGetConversationReturnsCustomizedConversation() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+
+        NotificationListenerService listenerService =
+                mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+
+        listenerService.onNotificationPosted(mStatusBarNotification);
+        shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+
+        assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID)).isNotNull();
+
+        listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
+                mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
+
+        assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID)).isNotNull();
+    }
+
+    @Test
+    public void testGetConversation() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+            TEST_SHORTCUT_ID)).isNull();
+
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        shortcut.setCached(ShortcutInfo.FLAG_PINNED);
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+        assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+            TEST_SHORTCUT_ID)).isNotNull();
+        assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID + "1")).isNull();
+
+        NotificationListenerService listenerService =
+                mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+        listenerService.onNotificationPosted(mStatusBarNotification);
+
+        ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID);
+        assertThat(result).isNotNull();
+        assertEquals(shortcut.getId(), result.getShortcutInfo().getId());
+        assertEquals(1, result.getShortcutInfo().getPersons().length);
+        assertEquals(CONTACT_URI, result.getShortcutInfo().getPersons()[0].getUri());
+        assertEquals(mParentNotificationChannel.getId(),
+                result.getParentNotificationChannel().getId());
+        assertEquals(mStatusBarNotification.getPostTime(), result.getLastEventTimestamp());
+        assertTrue(result.hasActiveNotifications());
+    }
+
+    @Test
+    public void testGetConversationGetsPersonsData() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        shortcut.setCached(ShortcutInfo.FLAG_PINNED);
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+
+        NotificationListenerService listenerService =
+                mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+        listenerService.onNotificationPosted(mStatusBarNotification);
+
+        ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID);
+
+        verify(mShortcutServiceInternal).getShortcuts(
+                anyInt(), anyString(), anyLong(), anyString(), anyList(), any(), any(),
+                mQueryFlagsCaptor.capture(), anyInt(), anyInt(), anyInt());
+        Integer queryFlags = mQueryFlagsCaptor.getValue();
+        assertThat(hasFlag(queryFlags, ShortcutQuery.FLAG_GET_PERSONS_DATA)).isTrue();
+    }
+
+    @Test
     public void testNotificationChannelCreated() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
         mDataManager.onUserUnlocked(USER_ID_SECONDARY);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index b0b8afd..df5b48a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -22,6 +22,8 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
@@ -978,6 +980,31 @@
         assertEquals(200, listener.mConfiguration.densityDpi);
     }
 
+    @Test
+    public void testFreezeInsetsStateWhenAppTransition() {
+        final Task stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+        final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
+        task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE);
+        spyOn(win);
+        doReturn(true).when(task).okToAnimate();
+        ArrayList<WindowContainer> sources = new ArrayList<>();
+        sources.add(activity);
+
+        // Simulate the task applying the exit transition, verify the main window of the task
+        // will be set the frozen insets state.
+        task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */,
+                false /* isVoiceInteraction */, sources);
+        verify(win).freezeInsetsState();
+
+        // Simulate the task transition finished, verify the frozen insets state of the window
+        // will be reset.
+        task.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION,
+                task.mSurfaceAnimator.getAnimation());
+        verify(win).clearFrozenInsetsState();
+    }
+
     /* Used so we can gain access to some protected members of the {@link WindowContainer} class */
     private static class TestWindowContainer extends WindowContainer<TestWindowContainer> {
         private final int mLayer;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 263aa19..3231f8b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -810,4 +810,27 @@
                 WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         assertFalse(sameTokenWindow.needsRelativeLayeringToIme());
     }
+
+    @Test
+    public void testSetFreezeInsetsState() {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        spyOn(app);
+        doReturn(true).when(app).isVisible();
+
+        // Set freezing the insets state to make the window ignore to dispatch insets changed.
+        final InsetsState expectedState = new InsetsState(app.getInsetsState(),
+                true /* copySources */);
+        app.freezeInsetsState();
+        assertEquals(expectedState, app.getFrozenInsetsState());
+        assertFalse(app.isReadyToDispatchInsetsState());
+        assertEquals(expectedState, app.getInsetsState());
+        mDisplayContent.getInsetsStateController().notifyInsetsChanged();
+        verify(app, never()).notifyInsetsChanged();
+
+        // Unfreeze the insets state to make the window can dispatch insets changed.
+        app.clearFrozenInsetsState();
+        assertTrue(app.isReadyToDispatchInsetsState());
+        mDisplayContent.getInsetsStateController().notifyInsetsChanged();
+        verify(app).notifyInsetsChanged();
+    }
 }