Merge "Inform Assistant visible activity when the stage of activity lifecycle is invoked." into tm-qpr-dev
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 855366a..81c3e89 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -87,10 +87,12 @@
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.os.StrictMode;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.service.voice.VoiceInteractionSession;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
@@ -154,6 +156,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IVoiceInteractionManagerService;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.ToolbarActionBar;
 import com.android.internal.app.WindowDecorActionBar;
@@ -1601,6 +1604,25 @@
         return callbacks;
     }
 
+    private void notifyVoiceInteractionManagerServiceActivityEvent(
+            @VoiceInteractionSession.VoiceInteractionActivityEventType int type) {
+
+        final IVoiceInteractionManagerService service =
+                IVoiceInteractionManagerService.Stub.asInterface(
+                        ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+        if (service == null) {
+            Log.w(TAG, "notifyVoiceInteractionManagerServiceActivityEvent: Can not get "
+                    + "VoiceInteractionManagerService");
+            return;
+        }
+
+        try {
+            service.notifyActivityEventChanged(mToken, type);
+        } catch (RemoteException e) {
+            // Empty
+        }
+    }
+
     /**
      * Called when the activity is starting.  This is where most initialization
      * should go: calling {@link #setContentView(int)} to inflate the
@@ -1876,6 +1898,9 @@
         mCalled = true;
 
         notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_START);
+
+        notifyVoiceInteractionManagerServiceActivityEvent(
+                VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_START);
     }
 
     /**
@@ -2019,6 +2044,12 @@
         final Window win = getWindow();
         if (win != null) win.makeActive();
         if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
+
+        // Because the test case "com.android.launcher3.jank.BinderTests#testPressHome" doesn't
+        // allow any binder call in onResume, we call this method in onPostResume.
+        notifyVoiceInteractionManagerServiceActivityEvent(
+                VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_RESUME);
+
         mCalled = true;
     }
 
@@ -2394,6 +2425,10 @@
         getAutofillClientController().onActivityPaused();
 
         notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_PAUSE);
+
+        notifyVoiceInteractionManagerServiceActivityEvent(
+                VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE);
+
         mCalled = true;
     }
 
@@ -2623,6 +2658,9 @@
 
         getAutofillClientController().onActivityStopped(mIntent, mChangingConfigurations);
         mEnterAnimationComplete = false;
+
+        notifyVoiceInteractionManagerServiceActivityEvent(
+                VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_STOP);
     }
 
     /**
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index f2ea060..c95a7de 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -731,10 +731,9 @@
      */
     public interface VoiceInteractionManagerProvider {
         /**
-         * Notifies the service when a high-level activity event has been changed, for example,
-         * an activity was resumed or stopped.
+         * Notifies the service when an activity is destroyed.
          */
-        void notifyActivityEventChanged();
+        void notifyActivityDestroyed(IBinder activityToken);
     }
 
     /**
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index df727e9..48f732e 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -19,6 +19,7 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -71,6 +72,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -143,6 +146,25 @@
      */
     public static final int SHOW_SOURCE_AUTOMOTIVE_SYSTEM_UI = 1 << 7;
 
+    /** @hide */
+    public static final int VOICE_INTERACTION_ACTIVITY_EVENT_START = 1;
+    /** @hide */
+    public static final int VOICE_INTERACTION_ACTIVITY_EVENT_RESUME = 2;
+    /** @hide */
+    public static final int VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE = 3;
+    /** @hide */
+    public static final int VOICE_INTERACTION_ACTIVITY_EVENT_STOP = 4;
+
+    /** @hide */
+    @IntDef(prefix = { "VOICE_INTERACTION_ACTIVITY_EVENT_" }, value = {
+            VOICE_INTERACTION_ACTIVITY_EVENT_START,
+            VOICE_INTERACTION_ACTIVITY_EVENT_RESUME,
+            VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE,
+            VOICE_INTERACTION_ACTIVITY_EVENT_STOP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface VoiceInteractionActivityEventType{}
+
     final Context mContext;
     final HandlerCaller mHandlerCaller;
 
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 681693b..bd51f12 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -18,6 +18,8 @@
 
 import android.content.ComponentName;
 import android.content.Intent;
+import android.hardware.soundtrigger.KeyphraseMetadata;
+import android.hardware.soundtrigger.SoundTrigger;
 import android.media.AudioFormat;
 import android.media.permission.Identity;
 import android.os.Bundle;
@@ -25,18 +27,17 @@
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.SharedMemory;
+import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import android.service.voice.IVoiceInteractionService;
+import android.service.voice.IVoiceInteractionSession;
+import android.service.voice.VisibleActivityInfo;
 
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
 import com.android.internal.app.IVoiceActionCheckCallback;
-import com.android.internal.app.IVoiceInteractionSessionShowCallback;
-import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.IVoiceInteractionSessionListener;
+import com.android.internal.app.IVoiceInteractionSessionShowCallback;
 import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
-import android.hardware.soundtrigger.KeyphraseMetadata;
-import android.hardware.soundtrigger.SoundTrigger;
-import android.service.voice.IVoiceInteractionService;
-import android.service.voice.IVoiceInteractionSession;
-import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import com.android.internal.app.IVoiceInteractor;
 
 interface IVoiceInteractionManagerService {
     void showSession(in Bundle sessionArgs, int flags);
@@ -289,4 +290,14 @@
      * Notifies when the session window is shown or hidden.
      */
     void setSessionWindowVisible(in IBinder token, boolean visible);
+
+    /**
+     * Notifies when the Activity lifecycle event changed.
+     *
+     * @param activityToken The token of activity.
+     * @param type The type of lifecycle event of the activity lifecycle.
+     */
+    oneway void notifyActivityEventChanged(
+            in IBinder activityToken,
+            int type);
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index df5113b..85d6f293 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2886,10 +2886,13 @@
                 || event == Event.ACTIVITY_DESTROYED)) {
             contentCaptureService.notifyActivityEvent(userId, activity, event);
         }
-        // TODO(b/201234353): Move the logic to client side.
-        if (mVoiceInteractionManagerProvider != null && (event == Event.ACTIVITY_PAUSED
-                || event == Event.ACTIVITY_RESUMED || event == Event.ACTIVITY_STOPPED)) {
-            mVoiceInteractionManagerProvider.notifyActivityEventChanged();
+        // Currently we have move most of logic to the client side. When the activity lifecycle
+        // event changed, the client side will notify the VoiceInteractionManagerService. But
+        // when the application process died, the VoiceInteractionManagerService will miss the
+        // activity lifecycle event changed, so we still need ACTIVITY_DESTROYED event here to
+        // know if the activity has been destroyed.
+        if (mVoiceInteractionManagerProvider != null && event == Event.ACTIVITY_DESTROYED) {
+            mVoiceInteractionManagerProvider.notifyActivityDestroyed(appToken);
         }
     }
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 352d8d1..bc5c9ec 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -172,11 +172,11 @@
         mAmInternal.setVoiceInteractionManagerProvider(
                 new ActivityManagerInternal.VoiceInteractionManagerProvider() {
                     @Override
-                    public void notifyActivityEventChanged() {
+                    public void notifyActivityDestroyed(IBinder activityToken) {
                         if (DEBUG) {
-                            Slog.d(TAG, "call notifyActivityEventChanged");
+                            Slog.d(TAG, "notifyActivityDestroyed activityToken=" + activityToken);
                         }
-                        mServiceStub.notifyActivityEventChanged();
+                        mServiceStub.notifyActivityDestroyed(activityToken);
                     }
                 });
     }
@@ -447,11 +447,12 @@
             return mImpl.supportsLocalVoiceInteraction();
         }
 
-        void notifyActivityEventChanged() {
+        void notifyActivityDestroyed(@NonNull IBinder activityToken) {
             synchronized (this) {
-                if (mImpl == null) return;
+                if (mImpl == null || activityToken == null) return;
 
-                Binder.withCleanCallingIdentity(() -> mImpl.notifyActivityEventChangedLocked());
+                Binder.withCleanCallingIdentity(
+                        () -> mImpl.notifyActivityDestroyedLocked(activityToken));
             }
         }
 
@@ -1223,6 +1224,16 @@
             }
         }
 
+        @Override
+        public void notifyActivityEventChanged(@NonNull IBinder activityToken, int type) {
+            synchronized (this) {
+                if (mImpl == null || activityToken == null) {
+                    return;
+                }
+                Binder.withCleanCallingIdentity(
+                        () -> mImpl.notifyActivityEventChangedLocked(activityToken, type));
+            }
+        }
         //----------------- Hotword Detection/Validation APIs --------------------------------//
 
         @Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index b9793ca..fabab25 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -520,9 +520,23 @@
         mActiveSession.stopListeningVisibleActivityChangedLocked();
     }
 
-    public void notifyActivityEventChangedLocked() {
+    public void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
         if (DEBUG) {
-            Slog.d(TAG, "notifyActivityEventChangedLocked");
+            Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
+        }
+        if (mActiveSession == null || !mActiveSession.mShown) {
+            if (DEBUG) {
+                Slog.d(TAG, "notifyActivityDestroyedLocked not allowed on no session or"
+                        + " hidden session");
+            }
+            return;
+        }
+        mActiveSession.notifyActivityDestroyedLocked(activityToken);
+    }
+
+    public void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyActivityEventChangedLocked type=" + type);
         }
         if (mActiveSession == null || !mActiveSession.mShown) {
             if (DEBUG) {
@@ -531,7 +545,7 @@
             }
             return;
         }
-        mActiveSession.notifyActivityEventChangedLocked();
+        mActiveSession.notifyActivityEventChangedLocked(activityToken, type);
     }
 
     public void updateStateLocked(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index ae9be8c..b24337f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -29,6 +29,7 @@
 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_TASK_ID;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.AppOpsManager;
@@ -59,6 +60,7 @@
 import android.service.voice.VisibleActivityInfo;
 import android.service.voice.VoiceInteractionService;
 import android.service.voice.VoiceInteractionSession;
+import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.IWindowManager;
 
@@ -128,7 +130,11 @@
     private boolean mListeningVisibleActivity;
     private final ScheduledExecutorService mScheduledExecutorService =
             Executors.newSingleThreadScheduledExecutor();
-    private final List<VisibleActivityInfo> mVisibleActivityInfos = new ArrayList<>();
+    // Records the visible activity information the system has already called onVisible, without
+    // confirming the result of callback. When activity visible state is changed, we use this to
+    // determine to call onVisible or onInvisible to assistant application.
+    private final ArrayMap<IBinder, VisibleActivityInfo> mVisibleActivityInfoForToken =
+            new ArrayMap<>();
     private final PowerManagerInternal mPowerManagerInternal;
     private final LowPowerStandbyControllerInternal mLowPowerStandbyControllerInternal;
     private final Runnable mRemoveFromLowPowerStandbyAllowlistRunnable =
@@ -530,7 +536,7 @@
 
     public void cancelLocked(boolean finishTask) {
         mListeningVisibleActivity = false;
-        mVisibleActivityInfos.clear();
+        mVisibleActivityInfoForToken.clear();
         hideLocked();
         mCanceled = true;
         if (mBound) {
@@ -608,17 +614,24 @@
         if (DEBUG) {
             Slog.d(TAG, "startListeningVisibleActivityChangedLocked");
         }
-        mListeningVisibleActivity = true;
-        mVisibleActivityInfos.clear();
 
-        mScheduledExecutorService.execute(() -> {
-            if (DEBUG) {
-                Slog.d(TAG, "call handleVisibleActivitiesLocked from enable listening");
-            }
-            synchronized (mLock) {
-                handleVisibleActivitiesLocked();
-            }
-        });
+        if (!mShown || mCanceled || mSession == null) {
+            return;
+        }
+
+        mListeningVisibleActivity = true;
+        mVisibleActivityInfoForToken.clear();
+
+        // It should only need to report which activities are visible
+        final ArrayMap<IBinder, VisibleActivityInfo> newVisibleActivityInfos =
+                getTopVisibleActivityInfosLocked();
+
+        if (newVisibleActivityInfos == null || newVisibleActivityInfos.isEmpty()) {
+            return;
+        }
+        notifyVisibleActivitiesChangedLocked(newVisibleActivityInfos,
+                VisibleActivityInfo.TYPE_ACTIVITY_ADDED);
+        mVisibleActivityInfoForToken.putAll(newVisibleActivityInfos);
     }
 
     void stopListeningVisibleActivityChangedLocked() {
@@ -626,12 +639,13 @@
             Slog.d(TAG, "stopListeningVisibleActivityChangedLocked");
         }
         mListeningVisibleActivity = false;
-        mVisibleActivityInfos.clear();
+        mVisibleActivityInfoForToken.clear();
     }
 
-    void notifyActivityEventChangedLocked() {
+    void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
         if (DEBUG) {
-            Slog.d(TAG, "notifyActivityEventChangedLocked");
+            Slog.d(TAG, "notifyActivityEventChangedLocked activityToken=" + activityToken
+                    + ", type=" + type);
         }
         if (!mListeningVisibleActivity) {
             if (DEBUG) {
@@ -640,99 +654,139 @@
             return;
         }
         mScheduledExecutorService.execute(() -> {
-            if (DEBUG) {
-                Slog.d(TAG, "call handleVisibleActivitiesLocked from activity event");
-            }
             synchronized (mLock) {
-                handleVisibleActivitiesLocked();
+                handleVisibleActivitiesLocked(activityToken, type);
             }
         });
     }
 
-    private List<VisibleActivityInfo> getVisibleActivityInfosLocked() {
+    private ArrayMap<IBinder, VisibleActivityInfo> getTopVisibleActivityInfosLocked() {
         if (DEBUG) {
-            Slog.d(TAG, "getVisibleActivityInfosLocked");
+            Slog.d(TAG, "getTopVisibleActivityInfosLocked");
         }
         List<ActivityAssistInfo> allVisibleActivities =
                 LocalServices.getService(ActivityTaskManagerInternal.class)
                         .getTopVisibleActivities();
         if (DEBUG) {
-            Slog.d(TAG,
-                    "getVisibleActivityInfosLocked: allVisibleActivities=" + allVisibleActivities);
+            Slog.d(TAG, "getTopVisibleActivityInfosLocked: allVisibleActivities="
+                    + allVisibleActivities);
         }
-        if (allVisibleActivities == null || allVisibleActivities.isEmpty()) {
+        if (allVisibleActivities.isEmpty()) {
             Slog.w(TAG, "no visible activity");
             return null;
         }
         final int count = allVisibleActivities.size();
-        final List<VisibleActivityInfo> visibleActivityInfos = new ArrayList<>(count);
+        final ArrayMap<IBinder, VisibleActivityInfo> visibleActivityInfoArrayMap =
+                new ArrayMap<>(count);
         for (int i = 0; i < count; i++) {
             ActivityAssistInfo info = allVisibleActivities.get(i);
             if (DEBUG) {
-                Slog.d(TAG, " : activityToken=" + info.getActivityToken()
+                Slog.d(TAG, "ActivityAssistInfo : activityToken=" + info.getActivityToken()
                         + ", assistToken=" + info.getAssistToken()
                         + ", taskId=" + info.getTaskId());
             }
-            visibleActivityInfos.add(
+            visibleActivityInfoArrayMap.put(info.getActivityToken(),
                     new VisibleActivityInfo(info.getTaskId(), info.getAssistToken()));
         }
-        return visibleActivityInfos;
+        return visibleActivityInfoArrayMap;
     }
 
-    private void handleVisibleActivitiesLocked() {
+    // TODO(b/242359988): Split this method up
+    private void handleVisibleActivitiesLocked(@NonNull IBinder activityToken, int type) {
         if (DEBUG) {
-            Slog.d(TAG, "handleVisibleActivitiesLocked");
+            Slog.d(TAG, "handleVisibleActivitiesLocked activityToken=" + activityToken
+                    + ", type=" + type);
         }
-        if (mSession == null) {
-            return;
-        }
-        if (!mShown || !mListeningVisibleActivity || mCanceled) {
-            return;
-        }
-        final List<VisibleActivityInfo> newVisibleActivityInfos = getVisibleActivityInfosLocked();
 
-        if (newVisibleActivityInfos == null || newVisibleActivityInfos.isEmpty()) {
-            notifyVisibleActivitiesChangedLocked(mVisibleActivityInfos,
-                    VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
-            mVisibleActivityInfos.clear();
+        if (!mListeningVisibleActivity) {
+            if (DEBUG) {
+                Slog.d(TAG, "not enable listening visible activity");
+            }
             return;
         }
-        if (mVisibleActivityInfos.isEmpty()) {
-            notifyVisibleActivitiesChangedLocked(newVisibleActivityInfos,
-                    VisibleActivityInfo.TYPE_ACTIVITY_ADDED);
-            mVisibleActivityInfos.addAll(newVisibleActivityInfos);
+        if (!mShown || mCanceled || mSession == null) {
             return;
         }
 
-        final List<VisibleActivityInfo> addedActivities = new ArrayList<>();
-        final List<VisibleActivityInfo> removedActivities = new ArrayList<>();
+        // We use this local variable to determine to call onVisible or onInvisible.
+        boolean notifyOnVisible = false;
+        VisibleActivityInfo notifyVisibleActivityInfo = null;
 
-        removedActivities.addAll(mVisibleActivityInfos);
-        for (int i = 0; i < newVisibleActivityInfos.size(); i++) {
-            final VisibleActivityInfo candidateVisibleActivityInfo = newVisibleActivityInfos.get(i);
-            if (!removedActivities.isEmpty() && removedActivities.contains(
-                    candidateVisibleActivityInfo)) {
-                removedActivities.remove(candidateVisibleActivityInfo);
-            } else {
-                addedActivities.add(candidateVisibleActivityInfo);
+        if (type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_START
+                || type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_RESUME) {
+            // It seems that the onStart is unnecessary. But if we have it, the assistant
+            // application can request the directActions early. Even if we have the onStart,
+            // we still need the onResume because it is possible that the activity goes to
+            // onResume from onPause with invisible before the activity goes to onStop from
+            // onPause.
+
+            // Check if we have reported this activity as visible. If we have reported it as
+            // visible, do nothing.
+            if (mVisibleActivityInfoForToken.containsKey(activityToken)) {
+                return;
+            }
+
+            // Before reporting this activity as visible, we need to make sure the activity
+            // is really visible.
+            notifyVisibleActivityInfo = getVisibleActivityInfoFromTopVisibleActivity(
+                    activityToken);
+            if (notifyVisibleActivityInfo == null) {
+                return;
+            }
+            notifyOnVisible = true;
+        } else if (type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE) {
+            // For the onPause stage, the Activity is not necessarily invisible now, so we need
+            // to check its state.
+            // Note: After syncing with Activity owner, before the onPause is called, the
+            // visibility state has been updated.
+            notifyVisibleActivityInfo = getVisibleActivityInfoFromTopVisibleActivity(
+                    activityToken);
+            if (notifyVisibleActivityInfo != null) {
+                return;
+            }
+
+            // Also make sure we previously reported this Activity as visible.
+            notifyVisibleActivityInfo = mVisibleActivityInfoForToken.get(activityToken);
+            if (notifyVisibleActivityInfo == null) {
+                return;
+            }
+        } else if (type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_STOP) {
+            // For the onStop stage, the activity is in invisible state. We only need to consider if
+            // we have reported this activity as visible. If we have reported it as visible, we
+            // need to report it as invisible.
+            // Why we still need onStop? Because it is possible that the activity is in a visible
+            // state during onPause stage, when the activity enters onStop from onPause, we may
+            // need to notify onInvisible.
+            // Note: After syncing with Activity owner, before the onStop is called, the
+            // visibility state has been updated.
+            notifyVisibleActivityInfo = mVisibleActivityInfoForToken.get(activityToken);
+            if (notifyVisibleActivityInfo == null) {
+                return;
+            }
+        } else {
+            Slog.w(TAG, "notifyActivityEventChangedLocked unexpected type=" + type);
+            return;
+        }
+
+        try {
+            mSession.notifyVisibleActivityInfoChanged(notifyVisibleActivityInfo,
+                    notifyOnVisible ? VisibleActivityInfo.TYPE_ACTIVITY_ADDED
+                            : VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
+        } catch (RemoteException e) {
+            if (DEBUG) {
+                Slog.w(TAG, "handleVisibleActivitiesLocked RemoteException : " + e);
             }
         }
 
-        if (!addedActivities.isEmpty()) {
-            notifyVisibleActivitiesChangedLocked(addedActivities,
-                    VisibleActivityInfo.TYPE_ACTIVITY_ADDED);
+        if (notifyOnVisible) {
+            mVisibleActivityInfoForToken.put(activityToken, notifyVisibleActivityInfo);
+        } else {
+            mVisibleActivityInfoForToken.remove(activityToken);
         }
-        if (!removedActivities.isEmpty()) {
-            notifyVisibleActivitiesChangedLocked(removedActivities,
-                    VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
-        }
-
-        mVisibleActivityInfos.clear();
-        mVisibleActivityInfos.addAll(newVisibleActivityInfos);
     }
 
     private void notifyVisibleActivitiesChangedLocked(
-            List<VisibleActivityInfo> visibleActivityInfos, int type) {
+            ArrayMap<IBinder, VisibleActivityInfo> visibleActivityInfos, int type) {
         if (visibleActivityInfos == null || visibleActivityInfos.isEmpty()) {
             return;
         }
@@ -741,7 +795,7 @@
         }
         try {
             for (int i = 0; i < visibleActivityInfos.size(); i++) {
-                mSession.notifyVisibleActivityInfoChanged(visibleActivityInfos.get(i), type);
+                mSession.notifyVisibleActivityInfoChanged(visibleActivityInfos.valueAt(i), type);
             }
         } catch (RemoteException e) {
             if (DEBUG) {
@@ -754,6 +808,51 @@
         }
     }
 
+    private VisibleActivityInfo getVisibleActivityInfoFromTopVisibleActivity(
+            @NonNull IBinder activityToken) {
+        final ArrayMap<IBinder, VisibleActivityInfo> visibleActivityInfos =
+                getTopVisibleActivityInfosLocked();
+        if (visibleActivityInfos == null) {
+            return null;
+        }
+        return visibleActivityInfos.get(activityToken);
+    }
+
+    void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
+        }
+        if (!mListeningVisibleActivity) {
+            if (DEBUG) {
+                Slog.d(TAG, "not enable listening visible activity");
+            }
+            return;
+        }
+        mScheduledExecutorService.execute(() -> {
+            synchronized (mLock) {
+                if (!mListeningVisibleActivity) {
+                    return;
+                }
+                if (!mShown || mCanceled || mSession == null) {
+                    return;
+                }
+
+                VisibleActivityInfo visibleActivityInfo = mVisibleActivityInfoForToken.remove(
+                        activityToken);
+                if (visibleActivityInfo != null) {
+                    try {
+                        mSession.notifyVisibleActivityInfoChanged(visibleActivityInfo,
+                                VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
+                    } catch (RemoteException e) {
+                        if (DEBUG) {
+                            Slog.w(TAG, "notifyVisibleActivityInfoChanged RemoteException : " + e);
+                        }
+                    }
+                }
+            }
+        });
+    }
+
     private void removeFromLowPowerStandbyAllowlist() {
         synchronized (mLock) {
             if (mLowPowerStandbyAllowlisted) {