• 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 package com.android.systemui.car.wm.scalableui.panel;
17 
18 
19 import static com.android.systemui.car.wm.scalableui.systemevents.SystemEventConstants.PANEL_TOKEN_ID;
20 import static com.android.systemui.car.wm.scalableui.systemevents.SystemEventConstants.SYSTEM_TASK_PANEL_EMPTY_EVENT_ID;
21 
22 import android.annotation.MainThread;
23 import android.app.ActivityManager;
24 import android.app.ActivityOptions;
25 import android.app.PendingIntent;
26 import android.car.app.CarActivityManager;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.os.Build;
31 import android.os.UserHandle;
32 import android.util.ArraySet;
33 import android.util.Log;
34 import android.view.SurfaceControl;
35 
36 import androidx.annotation.NonNull;
37 import androidx.annotation.Nullable;
38 import androidx.annotation.VisibleForTesting;
39 
40 import com.android.car.internal.dep.Trace;
41 
42 import com.android.car.scalableui.model.Event;
43 import com.android.car.scalableui.model.PanelState;
44 import com.android.car.scalableui.panel.Panel;
45 import com.android.systemui.car.CarServiceProvider;
46 import com.android.systemui.car.wm.scalableui.AutoTaskStackHelper;
47 import com.android.systemui.car.wm.scalableui.EventDispatcher;
48 import com.android.wm.shell.automotive.AutoTaskStackController;
49 import com.android.wm.shell.automotive.AutoTaskStackState;
50 import com.android.wm.shell.automotive.AutoTaskStackTransaction;
51 import com.android.wm.shell.automotive.RootTaskStack;
52 import com.android.wm.shell.automotive.RootTaskStackListener;
53 
54 import dagger.assisted.Assisted;
55 import dagger.assisted.AssistedFactory;
56 import dagger.assisted.AssistedInject;
57 
58 import java.util.Set;
59 
60 /**
61  * A {@link RootTaskStack} based implementation of a {@link Panel}.
62  */
63 public final class TaskPanel extends BasePanel {
64     private static final String TAG = TaskPanel.class.getSimpleName();
65     private static final String ROLE_TYPE_STRING = "string";
66     private static final String ROLE_TYPE_ARRAY = "array";
67     private static final boolean DEBUG = Build.isDebuggable();
68 
69     private final AutoTaskStackController mAutoTaskStackController;
70     private final CarServiceProvider mCarServiceProvider;
71     private final Set<ComponentName> mPersistedActivities;
72     private final AutoTaskStackHelper mAutoTaskStackHelper;
73     private final TaskPanelInfoRepository mTaskPanelInfoRepository;
74     private final EventDispatcher mEventDispatcher;
75 
76     private CarActivityManager mCarActivityManager;
77     private int mRootTaskId = -1;
78     private SurfaceControl mLeash;
79     private boolean mIsLaunchRoot;
80     private RootTaskStack mRootTaskStack;
81     private PanelUtils mPanelUtils;
82 
83     @AssistedInject
TaskPanel(AutoTaskStackController autoTaskStackController, @NonNull Context context, CarServiceProvider carServiceProvider, AutoTaskStackHelper autoTaskStackHelper, PanelUtils panelUtils, TaskPanelInfoRepository taskPanelInfoRepository, EventDispatcher dispatcher, @Assisted String id)84     public TaskPanel(AutoTaskStackController autoTaskStackController,
85             @NonNull Context context,
86             CarServiceProvider carServiceProvider,
87             AutoTaskStackHelper autoTaskStackHelper,
88             PanelUtils panelUtils,
89             TaskPanelInfoRepository taskPanelInfoRepository,
90             EventDispatcher dispatcher,
91             @Assisted String id) {
92         super(context, id);
93         mAutoTaskStackController = autoTaskStackController;
94         mCarServiceProvider = carServiceProvider;
95         mAutoTaskStackHelper = autoTaskStackHelper;
96         mTaskPanelInfoRepository = taskPanelInfoRepository;
97         mEventDispatcher = dispatcher;
98         mPersistedActivities = new ArraySet<>();
99         mPanelUtils = panelUtils;
100     }
101 
102     /**
103      * Initializes the panel with the RootTask. This must be called after the state has been set.
104      */
105     @Override
init()106     public void init() {
107         mCarServiceProvider.addListener(
108                 car -> {
109                     mCarActivityManager = car.getCarManager(CarActivityManager.class);
110                     trySetPersistentActivity();
111                 });
112 
113         mAutoTaskStackController.createRootTaskStack(getDisplayId(), getPanelId(),
114                 new RootTaskStackListener() {
115                     @Override
116                     public void onRootTaskStackCreated(@NonNull RootTaskStack rootTaskStack) {
117                         if (DEBUG) {
118                             Log.d(TAG, getPanelId() + ", onRootTaskStackCreated " + rootTaskStack);
119                         }
120                         mRootTaskStack = rootTaskStack;
121                         mRootTaskId = mRootTaskStack.getRootTaskInfo().taskId;
122                         trySetPersistentActivity();
123                         if (mIsLaunchRoot) {
124                             mAutoTaskStackController.setDefaultRootTaskStackOnDisplay(
125                                     getDisplayId(),
126                                     mRootTaskId);
127                         }
128 
129                         if (mPanelUtils.isUserUnlocked()) {
130                             reset();
131                         }
132                     }
133 
134                     @Override
135                     public void onRootTaskStackInfoChanged(@NonNull RootTaskStack rootTaskStack) {
136                         mRootTaskStack = rootTaskStack;
137                         mRootTaskId = mRootTaskStack.getRootTaskInfo().taskId;
138                     }
139 
140                     @Override
141                     public void onRootTaskStackDestroyed(@NonNull RootTaskStack rootTaskStack) {
142                         mRootTaskStack = null;
143                         mRootTaskId = -1;
144                     }
145 
146                     @Override
147                     public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
148                             SurfaceControl leash) {
149                         mAutoTaskStackHelper.setTaskUntrimmableIfNeeded(taskInfo);
150                         mTaskPanelInfoRepository.onTaskAppearedOnPanel(getId(), taskInfo);
151                     }
152 
153                     @Override
154                     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
155                         mTaskPanelInfoRepository.onTaskChangedOnPanel(getId(), taskInfo);
156                     }
157 
158                     @Override
159                     public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
160                         mTaskPanelInfoRepository.onTaskVanishedOnPanel(getId(), taskInfo);
161                         if (mRootTaskStack != null
162                                 && mRootTaskStack.getRootTaskInfo().numActivities == 0) {
163                             mEventDispatcher.executeTransaction(new Event.Builder(
164                                     SYSTEM_TASK_PANEL_EMPTY_EVENT_ID).addToken(PANEL_TOKEN_ID,
165                                     getPanelId()).build());
166                         }
167                     }
168                 });
169     }
170 
171     @Override
reset()172     public void reset() {
173         if (getRootStack() == null) {
174             Log.e(TAG, "Cannot reset when root stack is null for panel" + getPanelId());
175             return;
176         }
177         AutoTaskStackTransaction autoTaskStackTransaction = new AutoTaskStackTransaction();
178         AutoTaskStackState autoTaskStackState = new AutoTaskStackState(getBounds(), isVisible(),
179                 getLayer());
180         autoTaskStackTransaction.setTaskStackState(getRootStack().getId(), autoTaskStackState);
181         if (isVisible()) {
182             setBaseIntent(autoTaskStackTransaction);
183         }
184         mAutoTaskStackController.startTransition(autoTaskStackTransaction);
185     }
186 
setBaseIntent(AutoTaskStackTransaction autoTaskStackTransaction)187     private void setBaseIntent(AutoTaskStackTransaction autoTaskStackTransaction) {
188         if (getDefaultIntent() == null || getRootStack().getRootTaskInfo() == null) {
189             return;
190         }
191         Trace.beginSection(TAG + "#setBaseIntent");
192         Intent defaultIntent = getDefaultIntent();
193         ActivityOptions options = ActivityOptions.makeBasic();
194         options.setPendingIntentBackgroundActivityStartMode(
195                 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
196         options.setLaunchRootTask(getRootStack().getRootTaskInfo().token);
197 
198         PendingIntent pendingIntent = PendingIntent.getActivity(
199                 getContext().createContextAsUser(UserHandle.CURRENT, 0), 0, defaultIntent,
200                 PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
201         autoTaskStackTransaction.sendPendingIntent(pendingIntent, defaultIntent,
202                 options.toBundle());
203         Trace.endSection();
204     }
205 
206     @Nullable
getRootStack()207     public RootTaskStack getRootStack() {
208         return mRootTaskStack;
209     }
210 
211     /**
212      * Returns the task ID of the root task associated with this panel.
213      */
getRootTaskId()214     public int getRootTaskId() {
215         if (mRootTaskStack == null) {
216             return -1;
217         }
218         return mRootTaskStack.getRootTaskInfo().taskId;
219     }
220 
221     /**
222      * Returns the default intent associated with this {@link TaskPanel}.
223      *
224      * <p>The default intent will be send right after the TaskPanel is ready.
225      */
226     @Nullable
getDefaultIntent()227     public Intent getDefaultIntent() {
228         ComponentName componentName = mAutoTaskStackHelper.getDefaultIntent(getPanelId());
229         if (componentName == null) {
230             return null;
231         }
232         Intent defaultIntent = new Intent(Intent.ACTION_MAIN);
233         defaultIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
234         defaultIntent.setComponent(componentName);
235         return defaultIntent;
236     }
237 
getLeash()238     public SurfaceControl getLeash() {
239         return mLeash;
240     }
241 
setLeash(SurfaceControl leash)242     public void setLeash(SurfaceControl leash) {
243         mLeash = leash;
244     }
245 
246     /**
247      * Return whether this panel is the launch root panel.
248      */
isLaunchRoot()249     public boolean isLaunchRoot() {
250         return mIsLaunchRoot;
251     }
252 
253     @Override
setRole(int role)254     public void setRole(int role) {
255         if (getRole() == role) return;
256         super.setRole(role);
257         String roleTypeName = getContext().getResources().getResourceTypeName(getRole());
258         switch (roleTypeName) {
259             case ROLE_TYPE_STRING:
260                 String roleString = getContext().getResources().getString(getRole());
261                 if (PanelState.DEFAULT_ROLE.equals(roleString)) {
262                     mIsLaunchRoot = true;
263                     return;
264                 }
265                 mPersistedActivities.clear();
266                 ComponentName componentName = ComponentName.unflattenFromString(roleString);
267                 mPersistedActivities.add(componentName);
268                 break;
269             case ROLE_TYPE_ARRAY:
270                 mPersistedActivities.clear();
271                 String[] componentNameStrings = getContext().getResources().getStringArray(
272                         getRole());
273                 mPersistedActivities.addAll(convertToComponentNames(componentNameStrings));
274                 break;
275             default: {
276                 Log.e(TAG, "Role type is not supported " + roleTypeName);
277             }
278         }
279     }
280 
convertToComponentNames(String[] componentStrings)281     private ArraySet<ComponentName> convertToComponentNames(String[] componentStrings) {
282         ArraySet<ComponentName> componentNames = new ArraySet<>(componentStrings.length);
283         for (int i = componentStrings.length - 1; i >= 0; i--) {
284             componentNames.add(ComponentName.unflattenFromString(componentStrings[i]));
285         }
286         return componentNames;
287     }
288 
trySetPersistentActivity()289     private void trySetPersistentActivity() {
290         if (mCarActivityManager == null || mRootTaskStack == null) {
291             if (DEBUG) {
292                 Log.d(TAG,
293                         "mCarActivityManager or mRootTaskStack is null, [" + getId() + ","
294                                 + mCarActivityManager + ", " + mRootTaskStack + "]");
295             }
296             return;
297         }
298 
299         if (getRole() == 0) {
300             if (DEBUG) {
301                 Log.d(TAG, "mRole is 0, [" + getPanelId() + "]");
302             }
303             return;
304         }
305 
306         if (mIsLaunchRoot) {
307             if (DEBUG) {
308                 Log.d(TAG, "mIsLaunchRoot is true, [" + getPanelId() + "]");
309             }
310             return;
311         }
312 
313         mCarActivityManager.setPersistentActivitiesOnRootTask(
314                 mPersistedActivities.stream().toList(),
315                 mRootTaskStack.getRootTaskInfo().token.asBinder());
316     }
317 
318     @VisibleForTesting
setRootTaskStack(RootTaskStack rootTaskStack)319     void setRootTaskStack(RootTaskStack rootTaskStack) {
320         mRootTaskStack = rootTaskStack;
321     }
322 
323     @Override
toString()324     public String toString() {
325         return "TaskPanel{"
326                 + "mId='" + getPanelId() + '\''
327                 + ", mAlpha=" + getAlpha()
328                 + ", mIsVisible=" + isVisible()
329                 + ", mBounds=" + getBounds()
330                 + ", mRootTaskId=" + mRootTaskId
331                 + ", mContext=" + getContext()
332                 + ", mRole=" + getRole()
333                 + ", mLayer=" + getLayer()
334                 + ", mLeash=" + mLeash
335                 + ", mRootTaskStack=" + mRootTaskStack
336                 + ", mCornerRadius=" + getCornerRadius()
337                 + ", mIsLaunchRoot=" + mIsLaunchRoot
338                 + ", mDisplayId=" + getDisplayId()
339                 + '}';
340     }
341 
342     /**
343      * Checks if the activity with given {@link ComponentName} should show in current panel.
344      */
handles(@ullable ComponentName componentName)345     public boolean handles(@Nullable ComponentName componentName) {
346         return componentName != null && mPersistedActivities.contains(componentName);
347     }
348 
349     @AssistedFactory
350     public interface Factory {
351         /** Create instance of TaskPanel with specified id */
create(String id)352         TaskPanel create(String id);
353     }
354 }
355