Merge "Implement screen recording detection API internals" into main
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 36b74e3..7903050 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -69,12 +69,13 @@
 import android.view.displayhash.DisplayHash;
 import android.view.displayhash.VerifiedDisplayHash;
 import android.window.AddToSurfaceSyncGroupResult;
+import android.window.IScreenRecordingCallback;
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
-import android.window.ScreenCapture;
-import android.window.WindowContextInfo;
 import android.window.ITrustedPresentationListener;
+import android.window.ScreenCapture;
 import android.window.TrustedPresentationThresholds;
+import android.window.WindowContextInfo;
 
 /**
  * System private interface to the window manager.
@@ -1083,4 +1084,8 @@
 
 
     void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id);
+
+    boolean registerScreenRecordingCallback(IScreenRecordingCallback callback);
+
+    void unregisterScreenRecordingCallback(IScreenRecordingCallback callback);
 }
diff --git a/core/java/android/window/IScreenRecordingCallback.aidl b/core/java/android/window/IScreenRecordingCallback.aidl
new file mode 100644
index 0000000..560ee75
--- /dev/null
+++ b/core/java/android/window/IScreenRecordingCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2024 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.window;
+
+/**
+ * @hide
+ */
+oneway interface IScreenRecordingCallback {
+    void onScreenRecordingStateChanged(boolean visibleInScreenRecording);
+}
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 3c3c846..751c1a8 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -80,3 +80,11 @@
     is_fixed_read_only: true
     bug: "278757236"
 }
+
+flag {
+    namespace: "window_surfaces"
+    name: "screen_recording_callbacks"
+    description: "Enable screen recording callbacks public API"
+    is_fixed_read_only: true
+    bug: "304574518"
+}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 917a300..3a778c3 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -535,6 +535,12 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/ActivityStarter.java"
     },
+    "-1583619037": {
+      "message": "Failed to register MediaProjectionWatcherCallback",
+      "level": "ERROR",
+      "group": "WM_ERROR",
+      "at": "com\/android\/server\/wm\/ScreenRecordingCallbackController.java"
+    },
     "-1582845629": {
       "message": "Starting animation on %s",
       "level": "VERBOSE",
@@ -2983,12 +2989,6 @@
       "group": "WM_DEBUG_SCREEN_ON",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "466506262": {
-      "message": "Clear freezing of %s: visible=%b freezing=%b",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_ORIENTATION",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "485170982": {
       "message": "Not finishing noHistory %s on stop because we're just sleeping",
       "level": "DEBUG",
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index 8ce1b6d..3d927d3 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -121,7 +121,7 @@
     @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
                 + ".permission.MANAGE_MEDIA_PROJECTION)")
-    void addCallback(IMediaProjectionWatcherCallback callback);
+    MediaProjectionInfo addCallback(IMediaProjectionWatcherCallback callback);
 
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
diff --git a/media/java/android/media/projection/MediaProjectionInfo.java b/media/java/android/media/projection/MediaProjectionInfo.java
index ff60856..c820392 100644
--- a/media/java/android/media/projection/MediaProjectionInfo.java
+++ b/media/java/android/media/projection/MediaProjectionInfo.java
@@ -16,6 +16,7 @@
 
 package android.media.projection;
 
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
@@ -26,15 +27,18 @@
 public final class MediaProjectionInfo implements Parcelable {
     private final String mPackageName;
     private final UserHandle mUserHandle;
+    private final IBinder mLaunchCookie;
 
-    public MediaProjectionInfo(String packageName, UserHandle handle) {
+    public MediaProjectionInfo(String packageName, UserHandle handle, IBinder launchCookie) {
         mPackageName = packageName;
         mUserHandle = handle;
+        mLaunchCookie = launchCookie;
     }
 
     public MediaProjectionInfo(Parcel in) {
         mPackageName = in.readString();
         mUserHandle = UserHandle.readFromParcel(in);
+        mLaunchCookie = in.readStrongBinder();
     }
 
     public String getPackageName() {
@@ -45,12 +49,16 @@
         return mUserHandle;
     }
 
+    public IBinder getLaunchCookie() {
+        return mLaunchCookie;
+    }
+
     @Override
     public boolean equals(Object o) {
-        if (o instanceof MediaProjectionInfo) {
-            final MediaProjectionInfo other = (MediaProjectionInfo) o;
+        if (o instanceof MediaProjectionInfo other) {
             return Objects.equals(other.mPackageName, mPackageName)
-                    && Objects.equals(other.mUserHandle, mUserHandle);
+                    && Objects.equals(other.mUserHandle, mUserHandle)
+                    && Objects.equals(other.mLaunchCookie, mLaunchCookie);
         }
         return false;
     }
@@ -64,7 +72,8 @@
     public String toString() {
         return "MediaProjectionInfo{mPackageName="
             + mPackageName + ", mUserHandle="
-            + mUserHandle + "}";
+            + mUserHandle + ", mLaunchCookie"
+            + mLaunchCookie + "}";
     }
 
     @Override
@@ -76,6 +85,7 @@
     public void writeToParcel(Parcel out, int flags) {
         out.writeString(mPackageName);
         UserHandle.writeToParcel(mUserHandle, out);
+        out.writeStrongBinder(mLaunchCookie);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<MediaProjectionInfo> CREATOR =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
index 45f0a8c..44c411f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
@@ -66,6 +66,11 @@
 
         private const val DEFAULT_PACKAGE_NAME = "com.media.projection.test"
         private val DEFAULT_USER_HANDLE = UserHandle.getUserHandleForUid(UserHandle.myUserId())
-        private val DEFAULT_INFO = MediaProjectionInfo(DEFAULT_PACKAGE_NAME, DEFAULT_USER_HANDLE)
+        private val DEFAULT_INFO =
+            MediaProjectionInfo(
+                DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_HANDLE,
+                /* launchCookie = */ null
+            )
     }
 }
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index f6571d9..550aed5 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -304,7 +304,7 @@
     }
 
     @VisibleForTesting
-    void addCallback(final IMediaProjectionWatcherCallback callback) {
+    MediaProjectionInfo addCallback(final IMediaProjectionWatcherCallback callback) {
         IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
             @Override
             public void binderDied() {
@@ -314,6 +314,7 @@
         synchronized (mLock) {
             mCallbackDelegate.add(callback);
             linkDeathRecipientLocked(callback, deathRecipient);
+            return mProjectionGrant != null ? mProjectionGrant.getProjectionInfo() : null;
         }
     }
 
@@ -786,11 +787,11 @@
 
         @Override //Binder call
         @EnforcePermission(MANAGE_MEDIA_PROJECTION)
-        public void addCallback(final IMediaProjectionWatcherCallback callback) {
+        public MediaProjectionInfo addCallback(final IMediaProjectionWatcherCallback callback) {
             addCallback_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
-                MediaProjectionManagerService.this.addCallback(callback);
+                return MediaProjectionManagerService.this.addCallback(callback);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1244,7 +1245,7 @@
         }
 
         public MediaProjectionInfo getProjectionInfo() {
-            return new MediaProjectionInfo(packageName, userHandle);
+            return new MediaProjectionInfo(packageName, userHandle, mLaunchCookie);
         }
 
         boolean requiresForegroundService() {
diff --git a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
new file mode 100644
index 0000000..5f488b7
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2024 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.server.wm;
+
+import static android.content.Context.MEDIA_PROJECTION_SERVICE;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR;
+
+import android.media.projection.IMediaProjectionManager;
+import android.media.projection.IMediaProjectionWatcherCallback;
+import android.media.projection.MediaProjectionInfo;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.view.ContentRecordingSession;
+import android.window.IScreenRecordingCallback;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.Set;
+
+public class ScreenRecordingCallbackController {
+
+    private final class Callback implements IBinder.DeathRecipient {
+
+        IScreenRecordingCallback mCallback;
+        int mUid;
+
+        Callback(IScreenRecordingCallback callback, int uid) {
+            this.mCallback = callback;
+            this.mUid = uid;
+        }
+
+        public void binderDied() {
+            unregister(mCallback);
+        }
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private final Map<IBinder, Callback> mCallbacks = new ArrayMap<>();
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private final Map<Integer /*UID*/, Boolean> mLastInvokedStateByUid = new ArrayMap<>();
+
+    private final WindowManagerService mWms;
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private WindowContainer<WindowContainer> mRecordedWC;
+
+    private boolean mWatcherCallbackRegistered = false;
+
+    private final class MediaProjectionWatcherCallback extends
+            IMediaProjectionWatcherCallback.Stub {
+        @Override
+        public void onStart(MediaProjectionInfo mediaProjectionInfo) {
+            onScreenRecordingStart(mediaProjectionInfo);
+        }
+
+        @Override
+        public void onStop(MediaProjectionInfo mediaProjectionInfo) {
+            onScreenRecordingStop();
+        }
+
+        @Override
+        public void onRecordingSessionSet(MediaProjectionInfo mediaProjectionInfo,
+                ContentRecordingSession contentRecordingSession) {
+        }
+    }
+
+    ScreenRecordingCallbackController(WindowManagerService wms) {
+        mWms = wms;
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private void setRecordedWindowContainer(MediaProjectionInfo mediaProjectionInfo) {
+        if (mediaProjectionInfo.getLaunchCookie() == null) {
+            mRecordedWC = (WindowContainer) mWms.mRoot.getDefaultDisplay();
+        } else {
+            mRecordedWC = mWms.mRoot.getActivity(activity -> activity.mLaunchCookie
+                    == mediaProjectionInfo.getLaunchCookie()).getTask();
+        }
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private void ensureMediaProjectionWatcherCallbackRegistered() {
+        if (mWatcherCallbackRegistered) {
+            return;
+        }
+
+        IBinder binder = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
+        IMediaProjectionManager mediaProjectionManager =
+                IMediaProjectionManager.Stub.asInterface(binder);
+
+        long identityToken = Binder.clearCallingIdentity();
+        MediaProjectionInfo mediaProjectionInfo = null;
+        try {
+            mediaProjectionInfo = mediaProjectionManager.addCallback(
+                    new MediaProjectionWatcherCallback());
+            mWatcherCallbackRegistered = true;
+        } catch (RemoteException e) {
+            ProtoLog.e(WM_ERROR, "Failed to register MediaProjectionWatcherCallback");
+        } finally {
+            Binder.restoreCallingIdentity(identityToken);
+        }
+
+        if (mediaProjectionInfo != null) {
+            setRecordedWindowContainer(mediaProjectionInfo);
+        }
+    }
+
+    boolean register(IScreenRecordingCallback callback) {
+        synchronized (mWms.mGlobalLock) {
+            ensureMediaProjectionWatcherCallbackRegistered();
+
+            IBinder binder = callback.asBinder();
+            int uid = Binder.getCallingUid();
+
+            if (mCallbacks.containsKey(binder)) {
+                return mLastInvokedStateByUid.get(uid);
+            }
+
+            Callback callbackInfo = new Callback(callback, uid);
+            try {
+                binder.linkToDeath(callbackInfo, 0);
+            } catch (RemoteException e) {
+                return false;
+            }
+
+            boolean uidInRecording = uidHasRecordedActivity(callbackInfo.mUid);
+            mLastInvokedStateByUid.put(callbackInfo.mUid, uidInRecording);
+            mCallbacks.put(binder, callbackInfo);
+            return uidInRecording;
+        }
+    }
+
+    void unregister(IScreenRecordingCallback callback) {
+        synchronized (mWms.mGlobalLock) {
+            IBinder binder = callback.asBinder();
+            Callback callbackInfo = mCallbacks.remove(binder);
+            binder.unlinkToDeath(callbackInfo, 0);
+
+            boolean uidHasCallback = false;
+            for (Callback cb : mCallbacks.values()) {
+                if (cb.mUid == callbackInfo.mUid) {
+                    uidHasCallback = true;
+                    break;
+                }
+            }
+            if (!uidHasCallback) {
+                mLastInvokedStateByUid.remove(callbackInfo.mUid);
+            }
+        }
+    }
+
+    private void onScreenRecordingStart(MediaProjectionInfo mediaProjectionInfo) {
+        synchronized (mWms.mGlobalLock) {
+            setRecordedWindowContainer(mediaProjectionInfo);
+            dispatchCallbacks(getRecordedUids(), true /* visibleInScreenRecording*/);
+        }
+    }
+
+    private void onScreenRecordingStop() {
+        synchronized (mWms.mGlobalLock) {
+            dispatchCallbacks(getRecordedUids(), false /*visibleInScreenRecording*/);
+            mRecordedWC = null;
+        }
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    void onProcessActivityVisibilityChanged(int uid, boolean processVisible) {
+        // If recording isn't active or there's no registered callback for the uid, there's nothing
+        // to do on this visibility change.
+        if (mRecordedWC == null || !mLastInvokedStateByUid.containsKey(uid)) {
+            return;
+        }
+
+        // If the callbacks are already in the correct state, avoid making duplicate callbacks for
+        // the same state. This can happen when:
+        // * a process becomes visible but its UID already has a recorded activity from another
+        //   process.
+        // * a process becomes invisible but its UID already doesn't have any recorded activities.
+        if (processVisible == mLastInvokedStateByUid.get(uid)) {
+            return;
+        }
+
+        // If the process visibility change doesn't change the visibility of the UID, avoid making
+        // duplicate callbacks for the same state. This can happen when:
+        // * a process becomes visible but the newly visible activity isn't in the recorded window
+        //   container.
+        // * a process becomes invisible but there are still activities being recorded for the UID.
+        boolean uidInRecording = uidHasRecordedActivity(uid);
+        if ((processVisible && !uidInRecording) || (!processVisible && uidInRecording)) {
+            return;
+        }
+
+        dispatchCallbacks(Set.of(uid), processVisible);
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private boolean uidHasRecordedActivity(int uid) {
+        if (mRecordedWC == null) {
+            return false;
+        }
+        boolean[] hasRecordedActivity = {false};
+        mRecordedWC.forAllActivities(activityRecord -> {
+            if (activityRecord.getUid() == uid && activityRecord.isVisibleRequested()) {
+                hasRecordedActivity[0] = true;
+                return true;
+            }
+            return false;
+        }, true /*traverseTopToBottom*/);
+        return hasRecordedActivity[0];
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private Set<Integer> getRecordedUids() {
+        Set<Integer> result = new ArraySet<>();
+        if (mRecordedWC == null) {
+            return result;
+        }
+        mRecordedWC.forAllActivities(activityRecord -> {
+            if (activityRecord.isVisibleRequested() && mLastInvokedStateByUid.containsKey(
+                    activityRecord.getUid())) {
+                result.add(activityRecord.getUid());
+            }
+        }, true /*traverseTopToBottom*/);
+        return result;
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private void dispatchCallbacks(Set<Integer> uids, boolean visibleInScreenRecording) {
+        if (uids.isEmpty()) {
+            return;
+        }
+
+        for (Integer uid : uids) {
+            mLastInvokedStateByUid.put(uid, visibleInScreenRecording);
+        }
+
+        for (Callback callback : mCallbacks.values()) {
+            if (!uids.contains(callback.mUid)) {
+                continue;
+            }
+            try {
+                callback.mCallback.onScreenRecordingStateChanged(visibleInScreenRecording);
+            } catch (RemoteException e) {
+                // Client has died. Cleanup is handled via DeathRecipient.
+            }
+        }
+    }
+
+    void dump(PrintWriter pw) {
+        pw.format("ScreenRecordingCallbackController:\n");
+        pw.format("  Registered callbacks:\n");
+        for (Map.Entry<IBinder, Callback> entry : mCallbacks.entrySet()) {
+            pw.format("    callback=%s uid=%s\n", entry.getKey(), entry.getValue().mUid);
+        }
+        pw.format("  Last invoked states:\n");
+        for (Map.Entry<Integer, Boolean> entry : mLastInvokedStateByUid.entrySet()) {
+            pw.format("    uid=%s isVisibleInScreenRecording=%s\n", entry.getKey(),
+                    entry.getValue());
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b38dc00..9179acf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -303,6 +303,7 @@
 import android.view.inputmethod.ImeTracker;
 import android.window.AddToSurfaceSyncGroupResult;
 import android.window.ClientWindowFrames;
+import android.window.IScreenRecordingCallback;
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
 import android.window.ITrustedPresentationListener;
@@ -1104,6 +1105,8 @@
         void onAppFreezeTimeout();
     }
 
+    private final ScreenRecordingCallbackController mScreenRecordingCallbackController;
+
     public static WindowManagerService main(final Context context, final InputManagerService im,
             final boolean showBootMsgs, WindowManagerPolicy policy,
             ActivityTaskManagerService atm) {
@@ -1345,6 +1348,7 @@
         mBlurController = new BlurController(mContext, mPowerManager);
         mTaskFpsCallbackController = new TaskFpsCallbackController(mContext);
         mAccessibilityController = new AccessibilityController(this);
+        mScreenRecordingCallbackController = new ScreenRecordingCallbackController(this);
         mSystemPerformanceHinter = new SystemPerformanceHinter(mContext, displayId -> {
             synchronized (mGlobalLock) {
                 DisplayContent dc = mRoot.getDisplayContent(displayId);
@@ -7188,6 +7192,7 @@
             mSystemPerformanceHinter.dump(pw, "");
             mTrustedPresentationListenerController.dump(pw);
             mSensitiveContentPackages.dump(pw);
+            mScreenRecordingCallbackController.dump(pw);
         }
     }
 
@@ -9889,4 +9894,18 @@
             int id) {
         mTrustedPresentationListenerController.unregisterListener(listener, id);
     }
+
+    @Override
+    public boolean registerScreenRecordingCallback(IScreenRecordingCallback callback) {
+        return mScreenRecordingCallbackController.register(callback);
+    }
+
+    @Override
+    public void unregisterScreenRecordingCallback(IScreenRecordingCallback callback) {
+        mScreenRecordingCallbackController.unregister(callback);
+    }
+
+    void onProcessActivityVisibilityChanged(int uid, boolean visible) {
+        mScreenRecordingCallbackController.onProcessActivityVisibilityChanged(uid, visible);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index b8fa5e5..6d2e8cc 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1271,8 +1271,10 @@
                 & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
         if (!wasAnyVisible && anyVisible) {
             mAtm.mVisibleActivityProcessTracker.onAnyActivityVisible(this);
+            mAtm.mWindowManager.onProcessActivityVisibilityChanged(mUid, true /*visible*/);
         } else if (wasAnyVisible && !anyVisible) {
             mAtm.mVisibleActivityProcessTracker.onAllActivitiesInvisible(this);
+            mAtm.mWindowManager.onProcessActivityVisibilityChanged(mUid, false /*visible*/);
         } else if (wasAnyVisible && !wasResumed && hasResumedActivity()) {
             mAtm.mVisibleActivityProcessTracker.onActivityResumedWhileVisible(this);
         }