Merge "Add VDM onSecureWindowHidden API" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e766768..7bfa878 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3543,6 +3543,7 @@
   public static interface VirtualDeviceManager.ActivityListener {
     method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onActivityLaunchBlocked(int, @NonNull android.content.ComponentName, @NonNull android.os.UserHandle, @Nullable android.content.IntentSender);
     method public void onDisplayEmpty(int);
+    method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onSecureWindowHidden(int);
     method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onSecureWindowShown(int, @NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
     method @Deprecated public void onTopActivityChanged(int, @NonNull android.content.ComponentName);
     method public default void onTopActivityChanged(int, @NonNull android.content.ComponentName, int);
diff --git a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
index 767f52a..448793d 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
@@ -63,4 +63,11 @@
      * @param user The user associated with the activity.
      */
     void onSecureWindowShown(int displayId, in ComponentName componentName, in UserHandle user);
+
+    /**
+     * Called when a secure surface is no longer shown on the device.
+     *
+     * @param displayId The display ID on which the secure surface was shown.
+     */
+    void onSecureWindowHidden(int displayId);
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index d63a443..42c7441 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -166,6 +166,20 @@
                         Binder.restoreCallingIdentity(token);
                     }
                 }
+
+                @Override
+                public void onSecureWindowHidden(int displayId) {
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        synchronized (mActivityListenersLock) {
+                            for (int i = 0; i < mActivityListeners.size(); i++) {
+                                mActivityListeners.valueAt(i).onSecureWindowHidden(displayId);
+                            }
+                        }
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
             };
 
     private final IVirtualDeviceSoundEffectListener mSoundEffectListener =
@@ -617,6 +631,10 @@
             mExecutor.execute(() ->
                     mActivityListener.onSecureWindowShown(displayId, componentName, user));
         }
+
+        public void onSecureWindowHidden(int displayId) {
+            mExecutor.execute(() -> mActivityListener.onSecureWindowHidden(displayId));
+        }
     }
 
     /**
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 6ea7834..b3f09a9 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -1288,6 +1288,17 @@
         @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
         default void onSecureWindowShown(int displayId, @NonNull ComponentName componentName,
                 @NonNull UserHandle user) {}
+
+        /**
+         * Called when a window with a secure surface is no longer shown on the device.
+         *
+         * @param displayId The display ID on which the window was shown before.
+         *
+         * @see Display#FLAG_SECURE
+         * @see WindowManager.LayoutParams#FLAG_SECURE
+         */
+        @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
+        default void onSecureWindowHidden(int displayId) {}
     }
 
     /**
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 4b9065b..6069e34 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -88,6 +88,9 @@
         /** Called when a secure window shows on the virtual display. */
         void onSecureWindowShown(int displayId, @NonNull ActivityInfo activityInfo);
 
+        /** Called when a secure window is no longer shown on the virtual display. */
+        void onSecureWindowHidden(int displayId);
+
         /** Returns true when an intent should be intercepted */
         boolean shouldInterceptIntent(@NonNull Intent intent);
     }
@@ -123,6 +126,9 @@
     private boolean mIsMirrorDisplay = false;
     private final CountDownLatch mDisplayIdSetLatch = new CountDownLatch(1);
 
+    // Used for detecting changes in the window flags.
+    private int mCurrentWindowFlags = 0;
+
     @NonNull
     @GuardedBy("mGenericWindowPolicyControllerLock")
     private final ArraySet<Integer> mRunningUids = new ArraySet<>();
@@ -371,12 +377,19 @@
     public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
             int systemWindowFlags) {
         int displayId = waitAndGetDisplayId();
-        // The callback is fired only when windowFlags are changed. To let VirtualDevice owner
-        // aware that the virtual display has a secure window on top.
-        if ((windowFlags & FLAG_SECURE) != 0 && displayId != INVALID_DISPLAY) {
+        if (displayId != INVALID_DISPLAY) {
+            // The callback is fired only when windowFlags are changed. To let VirtualDevice owner
+            // aware that the virtual display has a secure window on top.
             // Post callback on the main thread, so it doesn't block activity launching.
-            mHandler.post(() -> mActivityListener.onSecureWindowShown(displayId, activityInfo));
+            if ((windowFlags & FLAG_SECURE) != 0 && (mCurrentWindowFlags & FLAG_SECURE) == 0) {
+                mHandler.post(
+                        () -> mActivityListener.onSecureWindowShown(displayId, activityInfo));
+            }
+            if ((windowFlags & FLAG_SECURE) == 0 && (mCurrentWindowFlags & FLAG_SECURE) != 0) {
+                mHandler.post(() -> mActivityListener.onSecureWindowHidden(displayId));
+            }
         }
+        mCurrentWindowFlags = windowFlags;
 
         if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
                 activityInfo.packageName,
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 8b5b93e..a1d621d 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -312,6 +312,17 @@
             }
         }
 
+        @Override
+        public void onSecureWindowHidden(int displayId) {
+            if (android.companion.virtualdevice.flags.Flags.activityControlApi()) {
+                try {
+                    mActivityListener.onSecureWindowHidden(displayId);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
+                }
+            }
+        }
+
         /**
          * Intercepts intent when matching any of the IntentFilter of any interceptor. Returns true
          * if the intent matches any filter notifying the DisplayPolicyController to abort the
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
index 1a593dd..42b7f4b 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
@@ -755,6 +755,7 @@
 
         verify(mActivityListener, after(TIMEOUT_MILLIS).never())
                 .onSecureWindowShown(eq(DISPLAY_ID), eq(activityInfo));
+        verify(mActivityListener, never()).onSecureWindowHidden(eq(DISPLAY_ID));
         verify(mActivityListener, never())
                 .onActivityLaunchBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
     }
@@ -776,6 +777,10 @@
                 .onSecureWindowShown(eq(DISPLAY_ID), eq(activityInfo));
         verify(mActivityListener, after(TIMEOUT_MILLIS).never())
                 .onActivityLaunchBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
+
+        assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, 0, 0)).isTrue();
+
+        verify(mActivityListener, timeout(TIMEOUT_MILLIS)).onSecureWindowHidden(eq(DISPLAY_ID));
     }
 
     @Test
@@ -794,6 +799,7 @@
 
         verify(mActivityListener, after(TIMEOUT_MILLIS).never())
                 .onSecureWindowShown(eq(DISPLAY_ID), eq(activityInfo));
+        verify(mActivityListener, never()).onSecureWindowHidden(eq(DISPLAY_ID));
         verify(mActivityListener, never())
                 .onActivityLaunchBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
     }