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