• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.car.wm.scalableui.panel;
18 
19 import android.app.ActivityManager;
20 import android.content.ComponentName;
21 import android.util.ArraySet;
22 
23 import com.android.car.scalableui.panel.Panel;
24 import com.android.car.scalableui.panel.PanelPool;
25 import com.android.systemui.dagger.qualifiers.UiBackground;
26 import com.android.wm.shell.dagger.WMSingleton;
27 
28 import java.util.HashMap;
29 import java.util.LinkedHashMap;
30 import java.util.Map;
31 import java.util.Objects;
32 import java.util.Set;
33 import java.util.concurrent.Executor;
34 
35 import javax.annotation.concurrent.GuardedBy;
36 import javax.inject.Inject;
37 
38 @WMSingleton
39 public class TaskPanelInfoRepository {
40     private final Object mLock = new Object();
41     @GuardedBy("mLock")
42     private final Map<String, LinkedHashMap<Integer, ActivityManager.RunningTaskInfo>>
43             mPanelTaskMap = new HashMap<>();
44     @GuardedBy("mLock")
45     private final Set<TaskPanelChangeListener> mListeners = new ArraySet<>();
46     private final Executor mUiBackgroundExecutor;
47 
48     @GuardedBy("mLock")
49     private boolean mHasPendingTaskChanges = false;
50 
51     @Inject
TaskPanelInfoRepository(@iBackground Executor executor)52     public TaskPanelInfoRepository(@UiBackground Executor executor) {
53         mUiBackgroundExecutor = executor;
54     }
55 
56     /**
57      * Add a change listener for all panels.
58      */
addChangeListener(TaskPanelChangeListener listener)59     public void addChangeListener(TaskPanelChangeListener listener) {
60         synchronized (mLock) {
61             mListeners.add(listener);
62         }
63     }
64 
65     /**
66      * Remove a change listener for all panels.
67      */
removeChangeListener(TaskPanelChangeListener listener)68     public void removeChangeListener(TaskPanelChangeListener listener) {
69         synchronized (mLock) {
70             mListeners.remove(listener);
71         }
72     }
73 
74     /**
75      * Query if a specific package is currently visible on any panel.
76      */
isPackageVisible(String packageName)77     public boolean isPackageVisible(String packageName) {
78         synchronized (mLock) {
79             for (String panelId : mPanelTaskMap.keySet()) {
80                 if (mPanelTaskMap.get(panelId).lastEntry() != null
81                         && mPanelTaskMap.get(panelId).lastEntry().getValue().topActivity != null
82                         && Objects.equals(mPanelTaskMap.get(panelId).lastEntry().getValue()
83                         .topActivity.getPackageName(), packageName)) {
84                     return isPanelVisible(panelId);
85                 }
86             }
87         }
88         return false;
89     }
90 
91     /**
92      * Query if a specific package is currently visible on a specific display.
93      */
isPackageVisibleOnDisplay(String packageName, int displayId)94     public boolean isPackageVisibleOnDisplay(String packageName, int displayId) {
95         synchronized (mLock) {
96             for (String panelId : mPanelTaskMap.keySet()) {
97                 if (mPanelTaskMap.get(panelId).lastEntry() != null) {
98                     ActivityManager.RunningTaskInfo taskInfo = mPanelTaskMap.get(
99                             panelId).lastEntry().getValue();
100                     if (taskInfo.topActivity != null && Objects.equals(
101                             taskInfo.topActivity.getPackageName(), packageName)
102                             && taskInfo.displayId == displayId) {
103                         return isPanelVisible(panelId);
104                     }
105                 }
106             }
107         }
108         return false;
109     }
110 
111     /**
112      * Query if a specific component is currently visible.
113      */
isComponentVisible(ComponentName componentName)114     public boolean isComponentVisible(ComponentName componentName) {
115         synchronized (mLock) {
116             for (String panelId : mPanelTaskMap.keySet()) {
117                 if (mPanelTaskMap.get(panelId).lastEntry() != null && Objects.equals(
118                         mPanelTaskMap.get(
119                                 panelId).lastEntry().getValue().topActivity, componentName)) {
120                     return isPanelVisible(panelId);
121                 }
122             }
123         }
124         return false;
125     }
126 
127     /**
128      * Query if a specific component is currently visible on a specific display.
129      */
isComponentVisibleOnDisplay(ComponentName componentName, int displayId)130     public boolean isComponentVisibleOnDisplay(ComponentName componentName, int displayId) {
131         synchronized (mLock) {
132             for (String panelId : mPanelTaskMap.keySet()) {
133                 if (mPanelTaskMap.get(panelId).lastEntry() != null && Objects.equals(
134                         mPanelTaskMap.get(
135                                 panelId).lastEntry().getValue().topActivity, componentName)
136                         && mPanelTaskMap.get(panelId).lastEntry().getValue().displayId
137                         == displayId) {
138                     return isPanelVisible(panelId);
139                 }
140             }
141         }
142         return false;
143     }
144 
145     /**
146      * Query if a specific panel is currently visible.
147      */
isPanelVisible(String panelId)148     private boolean isPanelVisible(String panelId) {
149         Panel panel = PanelPool.getInstance().getPanel(panelId);
150         if (panel == null) {
151             return false;
152         }
153         return panel.isVisible();
154     }
155 
onTaskAppearedOnPanel(String panelId, ActivityManager.RunningTaskInfo taskInfo)156     void onTaskAppearedOnPanel(String panelId, ActivityManager.RunningTaskInfo taskInfo) {
157         synchronized (mLock) {
158             if (!mPanelTaskMap.containsKey(panelId)) {
159                 mPanelTaskMap.put(panelId, new LinkedHashMap<>());
160             }
161             mPanelTaskMap.get(panelId).put(taskInfo.taskId, taskInfo);
162             mHasPendingTaskChanges = true;
163         }
164     }
165 
onTaskChangedOnPanel(String panelId, ActivityManager.RunningTaskInfo taskInfo)166     void onTaskChangedOnPanel(String panelId, ActivityManager.RunningTaskInfo taskInfo) {
167         synchronized (mLock) {
168             if (!mPanelTaskMap.containsKey(panelId)) {
169                 return;
170             }
171             ActivityManager.RunningTaskInfo oldTask = mPanelTaskMap.get(panelId).get(
172                     taskInfo.taskId);
173             mPanelTaskMap.get(panelId).put(taskInfo.taskId, taskInfo);
174             if ((oldTask == null || !isTaskVisible(oldTask)
175                     || !Objects.equals(oldTask.topActivity, taskInfo.topActivity))
176                     && isTaskVisible(taskInfo)) {
177                 mHasPendingTaskChanges = true;
178             }
179         }
180     }
181 
onTaskVanishedOnPanel(String panelId, ActivityManager.RunningTaskInfo taskInfo)182     void onTaskVanishedOnPanel(String panelId, ActivityManager.RunningTaskInfo taskInfo) {
183         synchronized (mLock) {
184             if (!mPanelTaskMap.containsKey(panelId)) {
185                 return;
186             }
187             ActivityManager.RunningTaskInfo removed = mPanelTaskMap.get(panelId).remove(
188                     taskInfo.taskId);
189             if (removed == null) {
190                 return;
191             }
192             mHasPendingTaskChanges = true;
193         }
194     }
195 
196     /**
197      * Notify if the top task on any panel has changed. This should be called from startAnimation
198      * only since that is when the task stack is finalized and settled (to reduce
199      * over-notification).
200      */
maybeNotifyTopTaskOnPanelChanged()201     public void maybeNotifyTopTaskOnPanelChanged() {
202         synchronized (mLock) {
203             if (!mHasPendingTaskChanges) {
204                 return;
205             }
206             mHasPendingTaskChanges = false;
207             mListeners.forEach(listener -> mUiBackgroundExecutor.execute(
208                     listener::onTopTaskOnPanelChanged));
209         }
210     }
211 
isTaskVisible(ActivityManager.RunningTaskInfo task)212     private boolean isTaskVisible(ActivityManager.RunningTaskInfo task) {
213         return task.isVisible && task.isRunning && !task.isSleeping;
214     }
215 
216     public interface TaskPanelChangeListener {
217         /**
218          * Notify the top task on a panel has changed.
219          */
onTopTaskOnPanelChanged()220         void onTopTaskOnPanelChanged();
221     }
222 }
223