Merge "Add Flaky annotation to 4 flaky tests in VibratorManagerServiceTest" into udc-qpr-dev
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index b6c39c6..16e94f8 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1015,6 +1015,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/NonAppWindowAnimationAdapter.java"
     },
+    "-1152771606": {
+      "message": "Content Recording: Display %d was already recording, but pause capture since the task is in PIP",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "-1145384901": {
       "message": "shouldWaitAnimatingExit: isTransition: %s",
       "level": "DEBUG",
@@ -2323,6 +2329,12 @@
       "group": "WM_DEBUG_SYNC_ENGINE",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "1877956": {
+      "message": "Content Recording: Display %d should start recording, but don't yet since the task is in PIP",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "3593205": {
       "message": "commitVisibility: %s: visible=%b mVisibleRequested=%b",
       "level": "VERBOSE",
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java
index 51eb9f7..c24e9dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java
@@ -36,7 +36,7 @@
 @SysUISingleton
 public class NotifInflationErrorManager {
 
-    Set<NotificationEntry> mErroredNotifs = new ArraySet<>();
+    Set<String> mErroredNotifs = new ArraySet<>();
     List<NotifInflationErrorListener> mListeners = new ArrayList<>();
 
     @Inject
@@ -48,7 +48,7 @@
      * @param e the exception encountered while inflating
      */
     public void setInflationError(NotificationEntry entry, Exception e) {
-        mErroredNotifs.add(entry);
+        mErroredNotifs.add(entry.getKey());
         for (int i = 0; i < mListeners.size(); i++) {
             mListeners.get(i).onNotifInflationError(entry, e);
         }
@@ -58,8 +58,8 @@
      * Notification inflated successfully and is no longer errored out.
      */
     public void clearInflationError(NotificationEntry entry) {
-        if (mErroredNotifs.contains(entry)) {
-            mErroredNotifs.remove(entry);
+        if (mErroredNotifs.contains(entry.getKey())) {
+            mErroredNotifs.remove(entry.getKey());
             for (int i = 0; i < mListeners.size(); i++) {
                 mListeners.get(i).onNotifInflationErrorCleared(entry);
             }
@@ -70,7 +70,7 @@
      * Whether or not the notification encountered an exception while inflating.
      */
     public boolean hasInflationError(@NonNull NotificationEntry entry) {
-        return mErroredNotifs.contains(entry);
+        return mErroredNotifs.contains(entry.getKey());
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt
new file mode 100644
index 0000000..e38adeb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager.NotifInflationErrorListener
+import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class NotifInflationErrorManagerTest : SysuiTestCase() {
+    private lateinit var manager: NotifInflationErrorManager
+
+    private val listener1 = mock(NotifInflationErrorListener::class.java)
+    private val listener2 = mock(NotifInflationErrorListener::class.java)
+
+    private val foo: NotificationEntry = NotificationEntryBuilder().setPkg("foo").build()
+    private val bar: NotificationEntry = NotificationEntryBuilder().setPkg("bar").build()
+    private val baz: NotificationEntry = NotificationEntryBuilder().setPkg("baz").build()
+
+    private val fooException = Exception("foo")
+    private val barException = Exception("bar")
+
+    @Before
+    fun setUp() {
+        // Reset manager instance before each test.
+        manager = NotifInflationErrorManager()
+    }
+
+    @Test
+    fun testTracksInflationErrors() {
+        manager.setInflationError(foo, fooException)
+        manager.setInflationError(bar, barException)
+
+        assertThat(manager.hasInflationError(foo)).isTrue()
+        assertThat(manager.hasInflationError(bar)).isTrue()
+        assertThat(manager.hasInflationError(baz)).isFalse()
+
+        manager.clearInflationError(bar)
+
+        assertThat(manager.hasInflationError(bar)).isFalse()
+    }
+
+    @Test
+    fun testNotifiesListeners() {
+        manager.addInflationErrorListener(listener1)
+        manager.setInflationError(foo, fooException)
+
+        verify(listener1).onNotifInflationError(foo, fooException)
+
+        manager.addInflationErrorListener(listener2)
+        manager.setInflationError(bar, barException)
+
+        verify(listener1).onNotifInflationError(bar, barException)
+        verify(listener2).onNotifInflationError(bar, barException)
+
+        manager.clearInflationError(foo)
+
+        verify(listener1).onNotifInflationErrorCleared(foo)
+        verify(listener2).onNotifInflationErrorCleared(foo)
+    }
+
+    @Test
+    fun testClearUnknownEntry() {
+        manager.addInflationErrorListener(listener1)
+        manager.clearInflationError(foo)
+
+        verify(listener1, never()).onNotifInflationErrorCleared(any())
+    }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 309a9c0..5b25e89 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -321,9 +321,6 @@
                             if (DEBUG) {
                                 Slog.d(TAG, "publish system wallpaper changed!");
                             }
-                            if (localSync != null) {
-                                localSync.complete();
-                            }
                             notifyWallpaperChanged(wallpaper);
                         }
                     };
@@ -331,7 +328,7 @@
                     // If this was the system wallpaper, rebind...
                     bindWallpaperComponentLocked(mImageWallpaper, true, false, wallpaper,
                             callback);
-                    notifyColorsWhich |= FLAG_SYSTEM;
+                    notifyColorsWhich |= wallpaper.mWhich;
                 }
 
                 if (lockWallpaperChanged) {
@@ -345,9 +342,6 @@
                             if (DEBUG) {
                                 Slog.d(TAG, "publish lock wallpaper changed!");
                             }
-                            if (localSync != null) {
-                                localSync.complete();
-                            }
                             notifyWallpaperChanged(wallpaper);
                         }
                     };
@@ -372,9 +366,8 @@
                 }
 
                 saveSettingsLocked(wallpaper.userId);
-                // Notify the client immediately if only lockscreen wallpaper changed.
-                if (lockWallpaperChanged && !sysWallpaperChanged) {
-                    notifyWallpaperChanged(wallpaper);
+                if ((sysWallpaperChanged || lockWallpaperChanged) && localSync != null) {
+                    localSync.complete();
                 }
             }
 
@@ -1383,7 +1376,6 @@
                             lockWp.connection.mWallpaper = lockWp;
                             mOriginalSystem.mWhich = FLAG_LOCK;
                             updateEngineFlags(mOriginalSystem);
-                            notifyWallpaperColorsChanged(lockWp, FLAG_LOCK);
                         } else {
                             // Failed rename, use current system wp for both
                             if (DEBUG) {
@@ -1403,7 +1395,6 @@
                         updateEngineFlags(mOriginalSystem);
                         mLockWallpaperMap.put(mNewWallpaper.userId, mOriginalSystem);
                         mLastLockWallpaper = mOriginalSystem;
-                        notifyWallpaperColorsChanged(mOriginalSystem, FLAG_LOCK);
                     }
                 } else if (mNewWallpaper.mWhich == FLAG_LOCK) {
                     // New wp is lock only, so old system+lock is now system only
@@ -1417,10 +1408,7 @@
                     }
                 }
             }
-
-            synchronized (mLock) {
-                saveSettingsLocked(mNewWallpaper.userId);
-            }
+            saveSettingsLocked(mNewWallpaper.userId);
 
             if (DEBUG) {
                 Slog.v(TAG, "--- wallpaper changed --");
@@ -3300,7 +3288,6 @@
                         if (DEBUG) {
                             Slog.d(TAG, "publish system wallpaper changed!");
                         }
-                        liveSync.complete();
                     }
                 };
 
@@ -3356,6 +3343,7 @@
                         }
                         mLockWallpaperMap.remove(newWallpaper.userId);
                     }
+                    if (liveSync != null) liveSync.complete();
                 }
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -3474,6 +3462,11 @@
         }
         // Has the component changed?
         if (!force && changingToSame(componentName, wallpaper)) {
+            try {
+                if (reply != null) reply.sendResult(null);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to send callback", e);
+            }
             return true;
         }
 
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index f0e4149..7cd07d6 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -151,6 +151,20 @@
                 return;
             }
 
+            // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are
+            //  inaccurate.
+            if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
+                final Task capturedTask = mRecordedWindowContainer.asTask();
+                if (capturedTask.inPinnedWindowingMode()) {
+                    ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+                            "Content Recording: Display %d was already recording, but "
+                                    + "pause capture since the task is in PIP",
+                            mDisplayContent.getDisplayId());
+                    pauseRecording();
+                    return;
+                }
+            }
+
             ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
                     "Content Recording: Display %d was already recording, so apply "
                             + "transformations if necessary",
@@ -292,6 +306,17 @@
             return;
         }
 
+        // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are inaccurate.
+        if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
+            if (mRecordedWindowContainer.asTask().inPinnedWindowingMode()) {
+                ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+                        "Content Recording: Display %d should start recording, but "
+                                + "don't yet since the task is in PIP",
+                        mDisplayContent.getDisplayId());
+                return;
+            }
+        }
+
         final Point surfaceSize = fetchSurfaceSizeIfPresent();
         if (surfaceSize == null) {
             ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
@@ -305,9 +330,6 @@
                         + "state %d",
                 mDisplayContent.getDisplayId(), mDisplayContent.getDisplayInfo().state);
 
-        // TODO(b/274790702): Do not start recording if waiting for consent - for now,
-        //  go ahead.
-
         // Create a mirrored hierarchy for the SurfaceControl of the DisplayArea to capture.
         mRecordedSurface = SurfaceControl.mirrorSurface(
                 mRecordedWindowContainer.getSurfaceControl());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index aa2b935..b8f6cb8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -360,6 +363,39 @@
     }
 
     @Test
+    public void testTaskWindowingModeChanged_pip_stopsRecording() {
+        // WHEN a recording is ongoing.
+        mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        // WHEN a configuration change arrives, and the task is now pinned.
+        mTask.setWindowingMode(WINDOWING_MODE_PINNED);
+        Configuration configuration = mTask.getConfiguration();
+        mTask.onConfigurationChanged(configuration);
+
+        // THEN recording is paused.
+        assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+    }
+
+    @Test
+    public void testTaskWindowingModeChanged_fullscreen_startsRecording() {
+        // WHEN a recording is ongoing.
+        mTask.setWindowingMode(WINDOWING_MODE_PINNED);
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+
+        // WHEN the task is now fullscreen.
+        mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        mContentRecorder.updateRecording();
+
+        // THEN recording is started.
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+    }
+
+    @Test
     public void testStartRecording_notifiesCallback_taskSession() {
         // WHEN a recording is ongoing.
         mContentRecorder.setContentRecordingSession(mTaskSession);
@@ -384,6 +420,45 @@
     }
 
     @Test
+    public void testStartRecording_taskInPIP_recordingNotStarted() {
+        // GIVEN a task is in PIP.
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mTask.setWindowingMode(WINDOWING_MODE_PINNED);
+
+        // WHEN a recording tries to start.
+        mContentRecorder.updateRecording();
+
+        // THEN recording does not start.
+        assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+    }
+
+    @Test
+    public void testStartRecording_taskInSplit_recordingStarted() {
+        // GIVEN a task is in PIP.
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+
+        // WHEN a recording tries to start.
+        mContentRecorder.updateRecording();
+
+        // THEN recording does not start.
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+    }
+
+    @Test
+    public void testStartRecording_taskInFullscreen_recordingStarted() {
+        // GIVEN a task is in PIP.
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+        // WHEN a recording tries to start.
+        mContentRecorder.updateRecording();
+
+        // THEN recording does not start.
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+    }
+
+    @Test
     public void testOnVisibleRequestedChanged_notifiesCallback() {
         // WHEN a recording is ongoing.
         mContentRecorder.setContentRecordingSession(mTaskSession);