/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.car.taskview; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.PendingIntent; import android.car.Car; import android.car.app.CarActivityManager; import android.car.app.CarTaskViewClient; import android.car.app.CarTaskViewHost; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Rect; import android.os.Binder; import android.os.Bundle; import android.os.DeadSystemRuntimeException; import android.util.Slog; import android.util.SparseArray; import android.view.InsetsSource; import android.view.SurfaceControl; import android.window.WindowContainerTransaction; import com.android.internal.annotations.Keep; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.taskview.TaskViewBase; import com.android.wm.shell.taskview.TaskViewTaskController; import com.android.wm.shell.taskview.TaskViewTransitions; /** Server side implementation for {@code RemoteCarTaskView}. */ public class RemoteCarTaskViewServerImpl implements TaskViewBase { private static final String TAG = RemoteCarTaskViewServerImpl.class.getSimpleName(); private final Context mContext; private final SyncTransactionQueue mSyncQueue; private final CarTaskViewClient mCarTaskViewClient; private final TaskViewTaskController mTaskViewTaskController; private final CarSystemUIProxyImpl mCarSystemUIProxy; private final Binder mInsetsOwner = new Binder(); private final SparseArray mInsets = new SparseArray<>(); private final ShellTaskOrganizer mShellTaskOrganizer; private final CarActivityManager mCarActivityManager; private RootTaskMediator mRootTaskMediator; private boolean mReleased; private final CarTaskViewHost mHostImpl = new CarTaskViewHost() { @Override public void release() { ensureManageSystemUIPermission(); if (mReleased) { Slog.w(TAG, "TaskView server part already released"); return; } mInsets.clear(); int taskIdToRemove = INVALID_TASK_ID; if (mTaskViewTaskController.getTaskInfo() != null) { taskIdToRemove = mTaskViewTaskController.getTaskInfo().taskId; } mTaskViewTaskController.release(); if (mRootTaskMediator != null) { mRootTaskMediator.release(); } else if (taskIdToRemove != INVALID_TASK_ID) { Slog.w(TAG, "Removing embedded task: " + taskIdToRemove); ActivityTaskManager.getInstance().removeTask(taskIdToRemove); } mCarSystemUIProxy.onCarTaskViewReleased(RemoteCarTaskViewServerImpl.this); mReleased = true; } @Override public void notifySurfaceCreated(SurfaceControl control) { ensureManageSystemUIPermission(); mTaskViewTaskController.surfaceCreated(control); } @Override public void setWindowBounds(Rect bounds) { ensureManageSystemUIPermission(); mTaskViewTaskController.setWindowBounds(bounds); } @Override public void notifySurfaceDestroyed() { ensureManageSystemUIPermission(); mTaskViewTaskController.surfaceDestroyed(); } @Override public void startActivity( PendingIntent pendingIntent, Intent fillInIntent, Bundle options, Rect launchBounds) { ensureManageSystemUIPermission(); mTaskViewTaskController.startActivity( pendingIntent, fillInIntent, ActivityOptions.fromBundle(options), launchBounds); } /** * Creates the root task in this task view. Should be called only once for the lifetime of * this task view. */ @Override // TODO(b/24087642): Remove @Keep once this method is promoted to SystemApi. // @Keep is used to prevent the removal of this method by the compiler as it is a hidden api // in the base class. @Keep public void createRootTask(int displayId) { ensureManageSystemUIPermission(); if (mRootTaskMediator != null) { throw new IllegalStateException("Root task is already created for this task view."); } mRootTaskMediator = new RootTaskMediator(displayId, /* isLaunchRoot= */ false, false, false, false, mShellTaskOrganizer, mTaskViewTaskController, RemoteCarTaskViewServerImpl.this, mSyncQueue, mCarActivityManager); } /** * Creates the launch root task in this task view. Should be called only once for the * lifetime of this task view. */ @Override // TODO(b/24087642): Remove @Keep once this method is promoted to SystemApi. // @Keep is used to prevent the removal of this method by the compiler as it is a hidden api // in the base class. @Keep public void createLaunchRootTask(int displayId, boolean embedHomeTask, boolean embedRecentsTask, boolean embedAssistantTask) { ensureManageSystemUIPermission(); if (mCarSystemUIProxy.isLaunchRootTaskPresent(displayId)) { throw new IllegalArgumentException("Cannot create more than 1 root task on the" + " display=" + displayId); } // TODO(b/299535374): Remove setHideTaskWithSurface once the taskviews with launch root // tasks are moved to an always visible window (surface) in SystemUI. mTaskViewTaskController.setHideTaskWithSurface(false); mRootTaskMediator = new RootTaskMediator(displayId, /* isLaunchRoot= */ true, embedHomeTask, embedRecentsTask, embedAssistantTask, mShellTaskOrganizer, mTaskViewTaskController, RemoteCarTaskViewServerImpl.this, mSyncQueue, mCarActivityManager); } @Override public void showEmbeddedTask() { ensureManageSystemUIPermission(); ActivityManager.RunningTaskInfo taskInfo = mTaskViewTaskController.getTaskInfo(); if (taskInfo == null) { return; } WindowContainerTransaction wct = new WindowContainerTransaction(); // Clears the hidden flag to make it TopFocusedRootTask: b/228092608 wct.setHidden(taskInfo.token, /* hidden= */ false); // Moves the embedded task to the top to make it resumed: b/225388469 wct.reorder(taskInfo.token, /* onTop= */ true); mSyncQueue.queue(wct); } @Override public void addInsets(int index, int type, @NonNull Rect frame) { ensureManageSystemUIPermission(); mInsets.append(InsetsSource.createId(mInsetsOwner, index, type), frame); if (mTaskViewTaskController.getTaskInfo() == null) { // The insets will be applied later as part of onTaskAppeared. Slog.w(TAG, "Cannot apply insets as the task token is not present."); return; } WindowContainerTransaction wct = new WindowContainerTransaction(); wct.addInsetsSource(mTaskViewTaskController.getTaskInfo().token, mInsetsOwner, index, type, frame); mSyncQueue.queue(wct); } @Override public void removeInsets(int index, int type) { ensureManageSystemUIPermission(); if (mInsets.size() == 0) { Slog.w(TAG, "No insets set."); return; } int id = InsetsSource.createId(mInsetsOwner, index, type); if (!mInsets.contains(id)) { Slog.w(TAG, "Insets type: " + type + " can't be removed as it was not " + "applied as part of the last addInsets()"); return; } mInsets.remove(id); if (mTaskViewTaskController.getTaskInfo() == null) { Slog.w(TAG, "Cannot remove insets as the task token is not present."); return; } WindowContainerTransaction wct = new WindowContainerTransaction(); wct.removeInsetsSource(mTaskViewTaskController.getTaskInfo().token, mInsetsOwner, index, type); mSyncQueue.queue(wct); } }; public RemoteCarTaskViewServerImpl( Context context, ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue, CarTaskViewClient carTaskViewClient, CarSystemUIProxyImpl carSystemUIProxy, TaskViewTransitions taskViewTransitions, CarActivityManager carActivityManager) { mContext = context; mSyncQueue = syncQueue; mCarTaskViewClient = carTaskViewClient; mCarSystemUIProxy = carSystemUIProxy; mShellTaskOrganizer = organizer; mCarActivityManager = carActivityManager; mTaskViewTaskController = new TaskViewTaskController(context, organizer, taskViewTransitions, syncQueue); mTaskViewTaskController.setTaskViewBase(this); } private void ensureManageSystemUIPermission() { if (Binder.getCallingPid() == android.os.Process.myPid()) { // If called from within CarSystemUI, allow. return; } if (mContext.checkCallingPermission(Car.PERMISSION_MANAGE_CAR_SYSTEM_UI) == PackageManager.PERMISSION_GRANTED) { return; } throw new SecurityException("requires permission " + Car.PERMISSION_MANAGE_CAR_SYSTEM_UI); } public CarTaskViewHost getHostImpl() { return mHostImpl; } boolean hasLaunchRootTaskOnDisplay(int display) { return mRootTaskMediator != null && mRootTaskMediator.isLaunchRoot() && mRootTaskMediator.getDisplayId() == display; } @Override public Rect getCurrentBoundsOnScreen() { try { return mCarTaskViewClient.getCurrentBoundsOnScreen(); } catch (DeadSystemRuntimeException ex) { Slog.w(TAG, "Failed to call getCurrentBoundsOnScreen() as TaskView client has " + "already died. Host part will be released shortly."); } return new Rect(0, 0, 0, 0); // If it reaches here, it means that // the host side is already being released so it doesn't matter what is returned from here. } @Override public String toString() { ActivityManager.RunningTaskInfo taskInfo = mTaskViewTaskController.getTaskInfo(); return "RemoteCarTaskViewServerImpl {" + "mInsets=" + mInsets + ", taskId=" + (taskInfo == null ? "null" : taskInfo.taskId) + ", taskInfo=" + (taskInfo == null ? "null" : taskInfo) + "}"; } @Override public void setResizeBgColor(SurfaceControl.Transaction transaction, int color) { try { mCarTaskViewClient.setResizeBackgroundColor(transaction, color); } catch (DeadSystemRuntimeException e) { Slog.w(TAG, "Failed to call setResizeBackgroundColor() as TaskView client has " + "already died. Host part will be released shortly."); } } @Override public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { applyAllInsets(); try { mCarTaskViewClient.onTaskAppeared(taskInfo, leash); } catch (DeadSystemRuntimeException e) { Slog.w(TAG, "Failed to call onTaskAppeared() as TaskView client has already died, " + "already died. Host part will be released shortly."); } } @Override public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { try { mCarTaskViewClient.onTaskInfoChanged(taskInfo); } catch (DeadSystemRuntimeException e) { Slog.w(TAG, "Failed to call onTaskInfoChanged() as TaskView client has already died, " + "already died. Host part will be released shortly."); } } @Override public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { try { mCarTaskViewClient.onTaskVanished(taskInfo); } catch (DeadSystemRuntimeException e) { Slog.w(TAG, "Failed to call onTaskVanished() as TaskView client has already died, " + "already died. Host part will be released shortly."); } } private void applyAllInsets() { if (mInsets.size() == 0) { Slog.w(TAG, "Cannot apply null or empty insets"); return; } if (mTaskViewTaskController.getTaskInfo() == null) { Slog.w(TAG, "Cannot apply insets as the task token is not present."); return; } WindowContainerTransaction wct = new WindowContainerTransaction(); for (int i = 0; i < mInsets.size(); i++) { final int id = mInsets.keyAt(i); final Rect frame = mInsets.valueAt(i); wct.addInsetsSource(mTaskViewTaskController.getTaskInfo().token, mInsetsOwner, InsetsSource.getIndex(id), InsetsSource.getType(id), frame); } mSyncQueue.queue(wct); } }