1 /* 2 * Copyright (C) 2023 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.taskview; 18 19 import static android.app.ActivityTaskManager.INVALID_TASK_ID; 20 21 import static com.android.systemui.car.wm.CarSystemUIProxyImpl.ensureManageSystemUIPermission; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.app.ActivityManager; 26 import android.app.ActivityOptions; 27 import android.app.ActivityTaskManager; 28 import android.app.PendingIntent; 29 import android.car.app.CarActivityManager; 30 import android.car.app.CarTaskViewClient; 31 import android.car.app.CarTaskViewHost; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.graphics.Rect; 35 import android.os.Binder; 36 import android.os.Bundle; 37 import android.os.DeadSystemRuntimeException; 38 import android.util.Slog; 39 import android.util.SparseArray; 40 import android.view.InsetsSource; 41 import android.view.SurfaceControl; 42 import android.window.WindowContainerTransaction; 43 44 import com.android.internal.annotations.Keep; 45 import com.android.systemui.car.wm.CarSystemUIProxyImpl; 46 import com.android.wm.shell.ShellTaskOrganizer; 47 import com.android.wm.shell.common.SyncTransactionQueue; 48 import com.android.wm.shell.taskview.TaskViewBase; 49 import com.android.wm.shell.taskview.TaskViewTaskController; 50 import com.android.wm.shell.taskview.TaskViewTransitions; 51 52 /** Server side implementation for {@code RemoteCarTaskView}. */ 53 public class RemoteCarTaskViewServerImpl implements TaskViewBase { 54 private static final String TAG = RemoteCarTaskViewServerImpl.class.getSimpleName(); 55 56 private final Context mContext; 57 private final CarTaskViewClient mCarTaskViewClient; 58 private final TaskViewTaskController mTaskViewTaskController; 59 private final CarSystemUIProxyImpl mCarSystemUIProxy; 60 private final Binder mInsetsOwner = new Binder(); 61 private final SparseArray<Rect> mInsets = new SparseArray<>(); 62 private final ShellTaskOrganizer mShellTaskOrganizer; 63 private final CarActivityManager mCarActivityManager; 64 private final TaskViewTransitions mTaskViewTransitions; 65 66 private RootTaskMediator mRootTaskMediator; 67 private boolean mReleased; 68 69 private final CarTaskViewHost mHostImpl = new CarTaskViewHost() { 70 @Override 71 public void release() { 72 ensureManageSystemUIPermission(mContext); 73 if (mReleased) { 74 Slog.w(TAG, "TaskView server part already released"); 75 return; 76 } 77 mInsets.clear(); 78 int taskIdToRemove = INVALID_TASK_ID; 79 if (mTaskViewTaskController.getTaskInfo() != null) { 80 taskIdToRemove = mTaskViewTaskController.getTaskInfo().taskId; 81 } 82 mTaskViewTaskController.release(); 83 84 if (mRootTaskMediator != null) { 85 mRootTaskMediator.release(); 86 } else if (taskIdToRemove != INVALID_TASK_ID) { 87 Slog.w(TAG, "Removing embedded task: " + taskIdToRemove); 88 ActivityTaskManager.getInstance().removeTask(taskIdToRemove); 89 } 90 mCarSystemUIProxy.onCarTaskViewReleased(RemoteCarTaskViewServerImpl.this); 91 mReleased = true; 92 } 93 94 @Override 95 public void notifySurfaceCreated(SurfaceControl control) { 96 ensureManageSystemUIPermission(mContext); 97 mTaskViewTaskController.surfaceCreated(control); 98 } 99 100 @Override 101 public void setWindowBounds(Rect bounds) { 102 ensureManageSystemUIPermission(mContext); 103 mTaskViewTaskController.setWindowBounds(bounds); 104 } 105 106 @Override 107 public void notifySurfaceDestroyed() { 108 ensureManageSystemUIPermission(mContext); 109 mTaskViewTaskController.surfaceDestroyed(); 110 } 111 112 @Override 113 public void startActivity( 114 PendingIntent pendingIntent, 115 Intent fillInIntent, 116 Bundle options, 117 Rect launchBounds) { 118 ensureManageSystemUIPermission(mContext); 119 ActivityOptions opt = ActivityOptions.fromBundle(options); 120 // Need this for the pending intent to work under BAL hardening. 121 opt.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true); 122 mTaskViewTaskController.startActivity( 123 pendingIntent, 124 fillInIntent, 125 opt, 126 launchBounds); 127 } 128 129 /** 130 * Creates the root task in this task view. Should be called only once for the lifetime of 131 * this task view. 132 */ 133 @Override 134 // TODO(b/24087642): Remove @Keep once this method is promoted to SystemApi. 135 // @Keep is used to prevent the removal of this method by the compiler as it is a hidden api 136 // in the base class. 137 @Keep 138 public void createRootTask(int displayId) { 139 ensureManageSystemUIPermission(mContext); 140 if (mRootTaskMediator != null) { 141 throw new IllegalStateException("Root task is already created for this task view."); 142 } 143 mRootTaskMediator = new RootTaskMediator(displayId, /* isLaunchRoot= */ false, 144 false, false, false, mShellTaskOrganizer, 145 mTaskViewTaskController, RemoteCarTaskViewServerImpl.this, 146 mCarActivityManager, mTaskViewTransitions); 147 } 148 149 /** 150 * Creates the launch root task in this task view. Should be called only once for the 151 * lifetime of this task view. 152 */ 153 @Override 154 // TODO(b/24087642): Remove @Keep once this method is promoted to SystemApi. 155 // @Keep is used to prevent the removal of this method by the compiler as it is a hidden api 156 // in the base class. 157 @Keep 158 public void createLaunchRootTask(int displayId, boolean embedHomeTask, 159 boolean embedRecentsTask, boolean embedAssistantTask) { 160 ensureManageSystemUIPermission(mContext); 161 if (mCarSystemUIProxy.isLaunchRootTaskPresent(displayId)) { 162 throw new IllegalArgumentException("Cannot create more than 1 root task on the" 163 + " display=" + displayId); 164 } 165 166 // TODO(b/299535374): Remove setHideTaskWithSurface once the taskviews with launch root 167 // tasks are moved to an always visible window (surface) in SystemUI. 168 mTaskViewTaskController.setHideTaskWithSurface(false); 169 170 mRootTaskMediator = new RootTaskMediator(displayId, /* isLaunchRoot= */ true, 171 embedHomeTask, embedRecentsTask, embedAssistantTask, mShellTaskOrganizer, 172 mTaskViewTaskController, RemoteCarTaskViewServerImpl.this, 173 mCarActivityManager, mTaskViewTransitions); 174 } 175 176 @Override 177 public void showEmbeddedTask() { 178 ensureManageSystemUIPermission(mContext); 179 ActivityManager.RunningTaskInfo taskInfo = mTaskViewTaskController.getTaskInfo(); 180 if (taskInfo == null) { 181 return; 182 } 183 if (mTaskViewTaskController.isUsingShellTransitions() && mTaskViewTransitions != null) { 184 mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, /* visible= */ 185 true, /* reorder= */ true); 186 return; 187 } 188 189 WindowContainerTransaction wct = new WindowContainerTransaction(); 190 // Clears the hidden flag to make it TopFocusedRootTask: b/228092608 191 wct.setHidden(taskInfo.token, /* hidden= */ false); 192 // Moves the embedded task to the top to make it resumed: b/225388469 193 wct.reorder(taskInfo.token, /* onTop= */ true); 194 mShellTaskOrganizer.applyTransaction(wct); 195 } 196 197 @Override 198 // TODO(b/24087642): Remove @Keep once this method is promoted to SystemApi. 199 // @Keep is used to prevent the removal of this method by the compiler as it is a hidden api 200 // in the base class. 201 @Keep 202 public void setTaskVisibility(boolean visibility) { 203 ensureManageSystemUIPermission(mContext); 204 ActivityManager.RunningTaskInfo taskInfo = mTaskViewTaskController.getTaskInfo(); 205 if (taskInfo == null) { 206 return; 207 } 208 if (mTaskViewTaskController.isUsingShellTransitions()) { 209 mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, visibility); 210 return; 211 } 212 213 WindowContainerTransaction wct = new WindowContainerTransaction(); 214 wct.setHidden(taskInfo.token, !visibility); 215 mShellTaskOrganizer.applyTransaction(wct); 216 } 217 218 @Override 219 // TODO(b/24087642): Remove @Keep once this method is promoted to SystemApi. 220 // @Keep is used to prevent the removal of this method by the compiler as it is a hidden api 221 // in the base class. 222 @Keep 223 public void reorderTask(boolean onTop) { 224 ensureManageSystemUIPermission(mContext); 225 ActivityManager.RunningTaskInfo taskInfo = mTaskViewTaskController.getTaskInfo(); 226 if (taskInfo == null) { 227 return; 228 } 229 230 if (mTaskViewTaskController.isUsingShellTransitions()) { 231 mTaskViewTransitions.reorderTaskViewTask(mTaskViewTaskController, onTop); 232 return; 233 } 234 235 WindowContainerTransaction wct = new WindowContainerTransaction(); 236 wct.reorder(taskInfo.token, onTop); 237 mShellTaskOrganizer.applyTransaction(wct); 238 } 239 240 @Override 241 public void addInsets(int index, int type, @NonNull Rect frame) { 242 ensureManageSystemUIPermission(mContext); 243 mInsets.append(InsetsSource.createId(mInsetsOwner, index, type), frame); 244 245 if (mTaskViewTaskController.getTaskInfo() == null) { 246 // The insets will be applied later as part of onTaskAppeared. 247 Slog.w(TAG, "Cannot apply insets as the task token is not present."); 248 return; 249 } 250 WindowContainerTransaction wct = new WindowContainerTransaction(); 251 wct.addInsetsSource(mTaskViewTaskController.getTaskInfo().token, 252 mInsetsOwner, index, type, frame, /* boundingRects = */ null); 253 mShellTaskOrganizer.applyTransaction(wct); 254 } 255 256 @Override 257 public void removeInsets(int index, int type) { 258 ensureManageSystemUIPermission(mContext); 259 if (mInsets.size() == 0) { 260 Slog.w(TAG, "No insets set."); 261 return; 262 } 263 int id = InsetsSource.createId(mInsetsOwner, index, type); 264 if (!mInsets.contains(id)) { 265 Slog.w(TAG, "Insets type: " + type + " can't be removed as it was not " 266 + "applied as part of the last addInsets()"); 267 return; 268 } 269 mInsets.remove(id); 270 271 if (mTaskViewTaskController.getTaskInfo() == null) { 272 Slog.w(TAG, "Cannot remove insets as the task token is not present."); 273 return; 274 } 275 WindowContainerTransaction wct = new WindowContainerTransaction(); 276 wct.removeInsetsSource(mTaskViewTaskController.getTaskInfo().token, 277 mInsetsOwner, index, type); 278 mShellTaskOrganizer.applyTransaction(wct); 279 } 280 }; 281 RemoteCarTaskViewServerImpl( Context context, ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue, CarTaskViewClient carTaskViewClient, CarSystemUIProxyImpl carSystemUIProxy, TaskViewTransitions taskViewTransitions, CarActivityManager carActivityManager)282 public RemoteCarTaskViewServerImpl( 283 Context context, 284 ShellTaskOrganizer organizer, 285 SyncTransactionQueue syncQueue, 286 CarTaskViewClient carTaskViewClient, 287 CarSystemUIProxyImpl carSystemUIProxy, 288 TaskViewTransitions taskViewTransitions, 289 CarActivityManager carActivityManager) { 290 mContext = context; 291 mCarTaskViewClient = carTaskViewClient; 292 mCarSystemUIProxy = carSystemUIProxy; 293 mShellTaskOrganizer = organizer; 294 mCarActivityManager = carActivityManager; 295 mTaskViewTransitions = taskViewTransitions; 296 297 mTaskViewTaskController = 298 new TaskViewTaskController(context, organizer, taskViewTransitions, syncQueue); 299 mTaskViewTaskController.setTaskViewBase(this); 300 } 301 getHostImpl()302 public CarTaskViewHost getHostImpl() { 303 return mHostImpl; 304 } 305 306 /** Returns true if a taskview with a launch root task exists on given {@code display}. */ hasLaunchRootTaskOnDisplay(int display)307 public boolean hasLaunchRootTaskOnDisplay(int display) { 308 return mRootTaskMediator != null && mRootTaskMediator.isLaunchRoot() 309 && mRootTaskMediator.getDisplayId() == display; 310 } 311 312 @Override getCurrentBoundsOnScreen()313 public Rect getCurrentBoundsOnScreen() { 314 try { 315 return mCarTaskViewClient.getCurrentBoundsOnScreen(); 316 } catch (DeadSystemRuntimeException ex) { 317 Slog.w(TAG, "Failed to call getCurrentBoundsOnScreen() as TaskView client has " 318 + "already died. Host part will be released shortly."); 319 } 320 return new Rect(0, 0, 0, 0); // If it reaches here, it means that 321 // the host side is already being released so it doesn't matter what is returned from here. 322 } 323 324 @Override toString()325 public String toString() { 326 ActivityManager.RunningTaskInfo taskInfo = mTaskViewTaskController.getTaskInfo(); 327 return "RemoteCarTaskViewServerImpl {" 328 + "insets=" + mInsets 329 + ", taskId=" + (taskInfo == null ? "null" : taskInfo.taskId) 330 + ", taskInfo=" + (taskInfo == null ? "null" : taskInfo) 331 + ", rootTaskMediator=" + (mRootTaskMediator == null ? "null" 332 : mRootTaskMediator.toString()) 333 + "}"; 334 } 335 336 @Override setResizeBgColor(SurfaceControl.Transaction transaction, int color)337 public void setResizeBgColor(SurfaceControl.Transaction transaction, int color) { 338 try { 339 mCarTaskViewClient.setResizeBackgroundColor(transaction, color); 340 } catch (DeadSystemRuntimeException e) { 341 Slog.w(TAG, "Failed to call setResizeBackgroundColor() as TaskView client has " 342 + "already died. Host part will be released shortly."); 343 } 344 } 345 346 @Override onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)347 public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { 348 applyAllInsets(); 349 try { 350 mCarTaskViewClient.onTaskAppeared(taskInfo, leash); 351 } catch (DeadSystemRuntimeException e) { 352 Slog.w(TAG, "Failed to call onTaskAppeared() as TaskView client has already died, " 353 + "already died. Host part will be released shortly."); 354 } 355 } 356 357 @Override onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)358 public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { 359 try { 360 mCarTaskViewClient.onTaskInfoChanged(taskInfo); 361 } catch (DeadSystemRuntimeException e) { 362 Slog.w(TAG, "Failed to call onTaskInfoChanged() as TaskView client has already died, " 363 + "already died. Host part will be released shortly."); 364 } 365 } 366 367 @Override onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)368 public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { 369 try { 370 mCarTaskViewClient.onTaskVanished(taskInfo); 371 } catch (DeadSystemRuntimeException e) { 372 Slog.w(TAG, "Failed to call onTaskVanished() as TaskView client has already died, " 373 + "already died. Host part will be released shortly."); 374 } 375 } 376 applyAllInsets()377 private void applyAllInsets() { 378 if (mInsets.size() == 0) { 379 Slog.w(TAG, "Cannot apply null or empty insets"); 380 return; 381 } 382 if (mTaskViewTaskController.getTaskInfo() == null) { 383 Slog.w(TAG, "Cannot apply insets as the task token is not present."); 384 return; 385 } 386 WindowContainerTransaction wct = new WindowContainerTransaction(); 387 for (int i = 0; i < mInsets.size(); i++) { 388 final int id = mInsets.keyAt(i); 389 final Rect frame = mInsets.valueAt(i); 390 wct.addInsetsSource(mTaskViewTaskController.getTaskInfo().token, 391 mInsetsOwner, InsetsSource.getIndex(id), InsetsSource.getType(id), frame, 392 null /* boundingRects */); 393 } 394 mShellTaskOrganizer.applyTransaction(wct); 395 } 396 397 /** 398 * Gets the task info of the running task. 399 */ 400 @Nullable getTaskInfo()401 ActivityManager.RunningTaskInfo getTaskInfo() { 402 if (mTaskViewTaskController == null) { 403 return null; 404 } 405 return mTaskViewTaskController.getTaskInfo(); 406 } 407 } 408