Merge "Track process with visible activities"
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 463ba6d..5dbaaaf 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1252,7 +1252,6 @@
 
     void setHasForegroundActivities(boolean hasForegroundActivities) {
         mHasForegroundActivities = hasForegroundActivities;
-        mWindowProcessController.setHasForegroundActivities(hasForegroundActivities);
     }
 
     boolean hasForegroundActivities() {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 4b5518c..33df8b8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -445,7 +445,6 @@
     private Task task;              // the task this is in.
     private long createTime = System.currentTimeMillis();
     long lastVisibleTime;         // last time this activity became visible
-    long cpuTimeAtResume;         // the cpu time of host process at the time of resuming activity
     long pauseTime;               // last time we started pausing the activity
     long launchTickTime;          // base time for launch tick messages
     long topResumedStateLossTime; // last time we reported top resumed state loss to an activity
@@ -5010,16 +5009,6 @@
         resumeKeyDispatchingLocked();
         final Task stack = getRootTask();
         mStackSupervisor.mNoAnimActivities.clear();
-
-        // Mark the point when the activity is resuming
-        // TODO: To be more accurate, the mark should be before the onCreate,
-        //       not after the onResume. But for subsequent starts, onResume is fine.
-        if (hasProcess()) {
-            cpuTimeAtResume = app.getCpuTime();
-        } else {
-            cpuTimeAtResume = 0; // Couldn't get the cpu time of process
-        }
-
         returningOptions = null;
 
         if (canTurnScreenOn()) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 73c4713..333455d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -366,6 +366,8 @@
     PendingIntentController mPendingIntentController;
     IntentFirewall mIntentFirewall;
 
+    final VisibleActivityProcessTracker mVisibleActivityProcessTracker;
+
     /* Global service lock used by the package the owns this service. */
     final WindowManagerGlobalLock mGlobalLock = new WindowManagerGlobalLock();
     /**
@@ -741,6 +743,7 @@
         mSystemThread = ActivityThread.currentActivityThread();
         mUiContext = mSystemThread.getSystemUiContext();
         mLifecycleManager = new ClientLifecycleManager();
+        mVisibleActivityProcessTracker = new VisibleActivityProcessTracker(this);
         mInternal = new LocalService();
         GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", GL_ES_VERSION_UNDEFINED);
         mWindowOrganizerController = new WindowOrganizerController(this);
@@ -6103,16 +6106,7 @@
 
         @Override
         public boolean hasResumedActivity(int uid) {
-            synchronized (mGlobalLock) {
-                final ArraySet<WindowProcessController> processes = mProcessMap.getProcesses(uid);
-                for (int i = 0, n = processes.size(); i < n; i++) {
-                    final WindowProcessController process = processes.valueAt(i);
-                    if (process.hasResumedActivity()) {
-                        return true;
-                    }
-                }
-            }
-            return false;
+            return mVisibleActivityProcessTracker.hasResumedActivity(uid);
         }
 
         public void setBackgroundActivityStartCallback(
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 0b2ba87..1255bb2 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -162,7 +162,6 @@
 import android.app.ActivityManager;
 import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityManager.TaskSnapshot;
-import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
 import android.app.AppGlobals;
@@ -5689,19 +5688,6 @@
 
         if (prev != null) {
             prev.resumeKeyDispatchingLocked();
-
-            if (prev.hasProcess() && prev.cpuTimeAtResume > 0) {
-                final long diff = prev.app.getCpuTime() - prev.cpuTimeAtResume;
-                if (diff > 0) {
-                    final Runnable r = PooledLambda.obtainRunnable(
-                            ActivityManagerInternal::updateForegroundTimeIfOnBattery,
-                            mAtmService.mAmInternal, prev.info.packageName,
-                            prev.info.applicationInfo.uid,
-                            diff);
-                    mAtmService.mH.post(r);
-                }
-            }
-            prev.cpuTimeAtResume = 0; // reset it
         }
 
         mRootWindowContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS);
diff --git a/services/core/java/com/android/server/wm/VisibleActivityProcessTracker.java b/services/core/java/com/android/server/wm/VisibleActivityProcessTracker.java
new file mode 100644
index 0000000..7f62630
--- /dev/null
+++ b/services/core/java/com/android/server/wm/VisibleActivityProcessTracker.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 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 android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A quick lookup for all processes with visible activities. It also tracks the CPU usage of
+ * host process with foreground (resumed) activity.
+ */
+class VisibleActivityProcessTracker {
+    @GuardedBy("mProcMap")
+    private final ArrayMap<WindowProcessController, CpuTimeRecord> mProcMap = new ArrayMap<>();
+    final Executor mBgExecutor = BackgroundThread.getExecutor();
+    final ActivityTaskManagerService mAtms;
+
+    VisibleActivityProcessTracker(ActivityTaskManagerService atms) {
+        mAtms = atms;
+    }
+
+    /** Called when any activity is visible in the process that didn't have one. */
+    void onAnyActivityVisible(WindowProcessController wpc) {
+        final CpuTimeRecord r = new CpuTimeRecord(wpc);
+        synchronized (mProcMap) {
+            mProcMap.put(wpc, r);
+        }
+        if (wpc.hasResumedActivity()) {
+            r.mShouldGetCpuTime = true;
+            mBgExecutor.execute(r);
+        }
+    }
+
+    /** Called when all visible activities of the process are no longer visible. */
+    void onAllActivitiesInvisible(WindowProcessController wpc) {
+        final CpuTimeRecord r = removeProcess(wpc);
+        if (r != null && r.mShouldGetCpuTime) {
+            mBgExecutor.execute(r);
+        }
+    }
+
+    /** Called when an activity is resumed on a process which is known to have visible activity. */
+    void onActivityResumedWhileVisible(WindowProcessController wpc) {
+        final CpuTimeRecord r;
+        synchronized (mProcMap) {
+            r = mProcMap.get(wpc);
+        }
+        if (r != null && !r.mShouldGetCpuTime) {
+            r.mShouldGetCpuTime = true;
+            mBgExecutor.execute(r);
+        }
+    }
+
+    boolean hasResumedActivity(int uid) {
+        synchronized (mProcMap) {
+            for (int i = mProcMap.size() - 1; i >= 0; i--) {
+                final WindowProcessController wpc = mProcMap.keyAt(i);
+                if (wpc.mUid == uid && wpc.hasResumedActivity()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    CpuTimeRecord removeProcess(WindowProcessController wpc) {
+        synchronized (mProcMap) {
+            return mProcMap.remove(wpc);
+        }
+    }
+
+    /**
+     * Get CPU time in background thread because it will access proc files or the lock of cpu
+     * tracker is held by a background thread.
+     */
+    private class CpuTimeRecord implements Runnable {
+        private final WindowProcessController mProc;
+        private long mCpuTime;
+        private boolean mHasStartCpuTime;
+        boolean mShouldGetCpuTime;
+
+        CpuTimeRecord(WindowProcessController wpc) {
+            mProc = wpc;
+        }
+
+        @Override
+        public void run() {
+            if (mProc.getPid() == 0) {
+                // The process is dead.
+                return;
+            }
+            if (!mHasStartCpuTime) {
+                mHasStartCpuTime = true;
+                mCpuTime = mProc.getCpuTime();
+            } else {
+                final long diff = mProc.getCpuTime() - mCpuTime;
+                if (diff > 0) {
+                    mAtms.mAmInternal.updateForegroundTimeIfOnBattery(
+                            mProc.mInfo.packageName, mProc.mInfo.uid, diff);
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 5bfa662..0431d11 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -137,8 +137,6 @@
     private volatile String mRequiredAbi;
     // Running any services that are foreground?
     private volatile boolean mHasForegroundServices;
-    // Running any activities that are foreground?
-    private volatile boolean mHasForegroundActivities;
     // Are there any client services with activities?
     private volatile boolean mHasClientActivities;
     // Is this process currently showing a non-activity UI that the user is interacting with?
@@ -226,10 +224,12 @@
     private final BackgroundActivityStartCallback mBackgroundActivityStartCallback;
 
     // The bits used for mActivityStateFlags.
-    private static final int ACTIVITY_STATE_FLAG_IS_VISIBLE = 0x10000000;
-    private static final int ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED = 0x20000000;
-    private static final int ACTIVITY_STATE_FLAG_IS_STOPPING = 0x40000000;
-    private static final int ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING = 0x80000000;
+    private static final int ACTIVITY_STATE_FLAG_IS_VISIBLE = 1 << 16;
+    private static final int ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED = 1 << 17;
+    private static final int ACTIVITY_STATE_FLAG_IS_STOPPING = 1 << 18;
+    private static final int ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING = 1 << 19;
+    private static final int ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE = 1 << 20;
+    private static final int ACTIVITY_STATE_FLAG_HAS_RESUMED = 1 << 21;
     private static final int ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER = 0x0000ffff;
 
     /**
@@ -281,6 +281,9 @@
             // configuration will update when display is ready.
             if (thread != null) {
                 setLastReportedConfiguration(getConfiguration());
+            } else {
+                // The process is inactive.
+                mAtm.mVisibleActivityProcessTracker.removeProcess(this);
             }
         }
     }
@@ -349,12 +352,10 @@
         return mHasForegroundServices;
     }
 
-    public void setHasForegroundActivities(boolean hasForegroundActivities) {
-        mHasForegroundActivities = hasForegroundActivities;
-    }
-
     boolean hasForegroundActivities() {
-        return mHasForegroundActivities;
+        return mAtm.mTopApp == this || (mActivityStateFlags
+                & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED
+                        | ACTIVITY_STATE_FLAG_IS_STOPPING)) != 0;
     }
 
     public void setHasClientActivities(boolean hasClientActivities) {
@@ -892,16 +893,9 @@
     }
 
     boolean hasResumedActivity() {
-        for (int i = mActivities.size() - 1; i >= 0; --i) {
-            final ActivityRecord activity = mActivities.get(i);
-            if (activity.isState(RESUMED)) {
-                return true;
-            }
-        }
-        return false;
+        return (mActivityStateFlags & ACTIVITY_STATE_FLAG_HAS_RESUMED) != 0;
     }
 
-
     void updateIntentForHeavyWeightActivity(Intent intent) {
         if (mActivities.isEmpty()) {
             return;
@@ -1041,10 +1035,14 @@
         // Since there could be more than one activities in a process record, we don't need to
         // compute the OomAdj with each of them, just need to find out the activity with the
         // "best" state, the order would be visible, pausing, stopping...
-        Task.ActivityState best = DESTROYED;
-        boolean finishing = true;
+        Task.ActivityState bestInvisibleState = DESTROYED;
+        boolean allStoppingFinishing = true;
         boolean visible = false;
         int minTaskLayer = Integer.MAX_VALUE;
+        int stateFlags = 0;
+        final boolean wasResumed = hasResumedActivity();
+        final boolean wasAnyVisible = (mActivityStateFlags
+                & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
         for (int i = mActivities.size() - 1; i >= 0; i--) {
             final ActivityRecord r = mActivities.get(i);
             if (r.app != this) {
@@ -1057,7 +1055,13 @@
                     continue;
                 }
             }
+            if (r.isVisible()) {
+                stateFlags |= ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE;
+            }
             if (r.mVisibleRequested) {
+                if (r.isState(RESUMED)) {
+                    stateFlags |= ACTIVITY_STATE_FLAG_HAS_RESUMED;
+                }
                 final Task task = r.getTask();
                 if (task != null && minTaskLayer > 0) {
                     final int layer = task.mLayerRank;
@@ -1069,29 +1073,39 @@
                 // continue the loop, in case there are multiple visible activities in
                 // this process, we'd find out the one with the minimal layer, thus it'll
                 // get a higher adj score.
-            } else if (best != PAUSING && best != PAUSED) {
+            } else if (!visible && bestInvisibleState != PAUSING) {
                 if (r.isState(PAUSING, PAUSED)) {
-                    best = PAUSING;
+                    bestInvisibleState = PAUSING;
                 } else if (r.isState(STOPPING)) {
-                    best = STOPPING;
+                    bestInvisibleState = STOPPING;
                     // Not "finishing" if any of activity isn't finishing.
-                    finishing &= r.finishing;
+                    allStoppingFinishing &= r.finishing;
                 }
             }
         }
 
-        int stateFlags = minTaskLayer & ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
+        stateFlags |= minTaskLayer & ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
         if (visible) {
             stateFlags |= ACTIVITY_STATE_FLAG_IS_VISIBLE;
-        } else if (best == PAUSING) {
+        } else if (bestInvisibleState == PAUSING) {
             stateFlags |= ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED;
-        } else if (best == STOPPING) {
+        } else if (bestInvisibleState == STOPPING) {
             stateFlags |= ACTIVITY_STATE_FLAG_IS_STOPPING;
-            if (finishing) {
+            if (allStoppingFinishing) {
                 stateFlags |= ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING;
             }
         }
         mActivityStateFlags = stateFlags;
+
+        final boolean anyVisible = (stateFlags
+                & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
+        if (!wasAnyVisible && anyVisible) {
+            mAtm.mVisibleActivityProcessTracker.onAnyActivityVisible(this);
+        } else if (wasAnyVisible && !anyVisible) {
+            mAtm.mVisibleActivityProcessTracker.onAllActivitiesInvisible(this);
+        } else if (wasAnyVisible && !wasResumed && hasResumedActivity()) {
+            mAtm.mVisibleActivityProcessTracker.onActivityResumedWhileVisible(this);
+        }
     }
 
     /** Called when the process has some oom related changes and it is going to update oom-adj. */
@@ -1643,6 +1657,32 @@
         pw.println(prefix + " Configuration=" + getConfiguration());
         pw.println(prefix + " OverrideConfiguration=" + getRequestedOverrideConfiguration());
         pw.println(prefix + " mLastReportedConfiguration=" + mLastReportedConfiguration);
+
+        final int stateFlags = mActivityStateFlags;
+        if (stateFlags != ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER) {
+            pw.print(prefix + " mActivityStateFlags=");
+            if ((stateFlags & ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE) != 0) {
+                pw.print("W|");
+            }
+            if ((stateFlags & ACTIVITY_STATE_FLAG_IS_VISIBLE) != 0) {
+                pw.print("V|");
+                if ((stateFlags & ACTIVITY_STATE_FLAG_HAS_RESUMED) != 0) {
+                    pw.print("R|");
+                }
+            } else if ((stateFlags & ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED) != 0) {
+                pw.print("P|");
+            } else if ((stateFlags & ACTIVITY_STATE_FLAG_IS_STOPPING) != 0) {
+                pw.print("S|");
+                if ((stateFlags & ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING) != 0) {
+                    pw.print("F|");
+                }
+            }
+            final int taskLayer = stateFlags & ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
+            if (taskLayer != ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER) {
+                pw.print("taskLayer=" + taskLayer);
+            }
+            pw.println();
+        }
     }
 
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index a7ced1d..c05eb8e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -662,8 +662,8 @@
         ai.uid = callingUid;
         ai.packageName = "com.android.test.package";
         final WindowProcessController callerApp =
-                new WindowProcessController(mAtm, ai, null, callingUid, -1, null, listener);
-        callerApp.setHasForegroundActivities(hasForegroundActivities);
+                spy(new WindowProcessController(mAtm, ai, null, callingUid, -1, null, listener));
+        doReturn(hasForegroundActivities).when(callerApp).hasForegroundActivities();
         doReturn(callerApp).when(mAtm).getProcessController(caller);
         // caller is recents
         RecentTasks recentTasks = mock(RecentTasks.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 21be6ef..5afcedb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -22,6 +22,9 @@
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.Display.INVALID_DISPLAY;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -168,11 +171,7 @@
                 mAtm, applicationInfo, null, 0, -1, null, mMockListener);
         wpc.setThread(mock(IApplicationThread.class));
 
-        final ActivityRecord activity = new ActivityBuilder(mAtm)
-                .setCreateTask(true)
-                .setUseProcess(wpc)
-                .build();
-
+        final ActivityRecord activity = createActivityRecord(wpc);
         wpc.addActivityIfNeeded(activity);
         // System UI owned processes should not be registered for activity config changes.
         assertFalse(wpc.registeredForActivityConfigChanges());
@@ -185,11 +184,7 @@
         // Notify WPC that this process has started an IME service.
         mWpc.onServiceStarted(serviceInfo);
 
-        final ActivityRecord activity = new ActivityBuilder(mAtm)
-                .setCreateTask(true)
-                .setUseProcess(mWpc)
-                .build();
-
+        final ActivityRecord activity = createActivityRecord(mWpc);
         mWpc.addActivityIfNeeded(activity);
         // IME processes should not be registered for activity config changes.
         assertFalse(mWpc.registeredForActivityConfigChanges());
@@ -202,11 +197,7 @@
         // Notify WPC that this process has started an ally service.
         mWpc.onServiceStarted(serviceInfo);
 
-        final ActivityRecord activity = new ActivityBuilder(mAtm)
-                .setCreateTask(true)
-                .setUseProcess(mWpc)
-                .build();
-
+        final ActivityRecord activity = createActivityRecord(mWpc);
         mWpc.addActivityIfNeeded(activity);
         // Ally processes should not be registered for activity config changes.
         assertFalse(mWpc.registeredForActivityConfigChanges());
@@ -219,11 +210,7 @@
         // Notify WPC that this process has started an voice interaction service.
         mWpc.onServiceStarted(serviceInfo);
 
-        final ActivityRecord activity = new ActivityBuilder(mAtm)
-                .setCreateTask(true)
-                .setUseProcess(mWpc)
-                .build();
-
+        final ActivityRecord activity = createActivityRecord(mWpc);
         mWpc.addActivityIfNeeded(activity);
         // Voice interaction service processes should not be registered for activity config changes.
         assertFalse(mWpc.registeredForActivityConfigChanges());
@@ -244,7 +231,7 @@
         final int globalSeq = 100;
         mRootWindowContainer.getConfiguration().seq = globalSeq;
         invertOrientation(mWpc.getConfiguration());
-        new ActivityBuilder(mAtm).setCreateTask(true).setUseProcess(mWpc).build();
+        createActivityRecord(mWpc);
 
         assertTrue(mWpc.registeredForActivityConfigChanges());
         assertEquals("Config seq of process should not be affected by activity",
@@ -253,10 +240,7 @@
 
     @Test
     public void testComputeOomAdjFromActivities() {
-        final ActivityRecord activity = new ActivityBuilder(mAtm)
-                .setCreateTask(true)
-                .setUseProcess(mWpc)
-                .build();
+        final ActivityRecord activity = createActivityRecord(mWpc);
         activity.mVisibleRequested = true;
         final int[] callbackResult = { 0 };
         final int visible = 1;
@@ -308,6 +292,41 @@
         assertEquals(other, callbackResult[0]);
     }
 
+    @Test
+    public void testComputeProcessActivityState() {
+        final VisibleActivityProcessTracker tracker = mAtm.mVisibleActivityProcessTracker;
+        spyOn(tracker);
+        final ActivityRecord activity = createActivityRecord(mWpc);
+        activity.mVisibleRequested = true;
+        activity.setState(Task.ActivityState.STARTED, "test");
+
+        verify(tracker).onAnyActivityVisible(mWpc);
+        assertTrue(mWpc.hasVisibleActivities());
+
+        activity.setState(Task.ActivityState.RESUMED, "test");
+
+        verify(tracker).onActivityResumedWhileVisible(mWpc);
+        assertTrue(tracker.hasResumedActivity(mWpc.mUid));
+
+        activity.makeFinishingLocked();
+        activity.setState(Task.ActivityState.PAUSING, "test");
+
+        assertFalse(tracker.hasResumedActivity(mWpc.mUid));
+        assertTrue(mWpc.hasForegroundActivities());
+
+        activity.setVisibility(false);
+        activity.mVisibleRequested = false;
+        activity.setState(Task.ActivityState.STOPPED, "test");
+
+        verify(tracker).onAllActivitiesInvisible(mWpc);
+        assertFalse(mWpc.hasVisibleActivities());
+        assertFalse(mWpc.hasForegroundActivities());
+    }
+
+    private ActivityRecord createActivityRecord(WindowProcessController wpc) {
+        return new ActivityBuilder(mAtm).setCreateTask(true).setUseProcess(wpc).build();
+    }
+
     private TestDisplayContent createTestDisplayContentInContainer() {
         return new TestDisplayContent.Builder(mAtm, 1000, 1500).build();
     }