1 /*
2  * Copyright (C) 2020 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.wm.shell.fullscreen;
18 
19 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
20 import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
21 
22 import android.app.ActivityManager;
23 import android.app.ActivityManager.RunningTaskInfo;
24 import android.graphics.Point;
25 import android.util.SparseArray;
26 import android.view.SurfaceControl;
27 
28 import androidx.annotation.NonNull;
29 
30 import com.android.internal.protolog.common.ProtoLog;
31 import com.android.wm.shell.ShellTaskOrganizer;
32 import com.android.wm.shell.common.SyncTransactionQueue;
33 import com.android.wm.shell.protolog.ShellProtoLogGroup;
34 import com.android.wm.shell.recents.RecentTasksController;
35 import com.android.wm.shell.sysui.ShellInit;
36 import com.android.wm.shell.transition.Transitions;
37 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
38 
39 import java.io.PrintWriter;
40 import java.util.Optional;
41 
42 /**
43   * Organizes tasks presented in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN}.
44  * @param <T> the type of window decoration instance
45   */
46 public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
47     private static final String TAG = "FullscreenTaskListener";
48 
49     private final ShellTaskOrganizer mShellTaskOrganizer;
50 
51     private final SparseArray<State> mTasks = new SparseArray<>();
52 
53     private static class State {
54         RunningTaskInfo mTaskInfo;
55         SurfaceControl mLeash;
56     }
57     private final SyncTransactionQueue mSyncQueue;
58     private final Optional<RecentTasksController> mRecentTasksOptional;
59     private final Optional<WindowDecorViewModel> mWindowDecorViewModelOptional;
60     /**
61      * This constructor is used by downstream products.
62      */
FullscreenTaskListener(SyncTransactionQueue syncQueue)63     public FullscreenTaskListener(SyncTransactionQueue syncQueue) {
64         this(null /* shellInit */, null /* shellTaskOrganizer */, syncQueue, Optional.empty(),
65                 Optional.empty());
66     }
67 
FullscreenTaskListener(ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, Optional<RecentTasksController> recentTasksOptional, Optional<WindowDecorViewModel> windowDecorViewModelOptional)68     public FullscreenTaskListener(ShellInit shellInit,
69             ShellTaskOrganizer shellTaskOrganizer,
70             SyncTransactionQueue syncQueue,
71             Optional<RecentTasksController> recentTasksOptional,
72             Optional<WindowDecorViewModel> windowDecorViewModelOptional) {
73         mShellTaskOrganizer = shellTaskOrganizer;
74         mSyncQueue = syncQueue;
75         mRecentTasksOptional = recentTasksOptional;
76         mWindowDecorViewModelOptional = windowDecorViewModelOptional;
77         // Note: Some derivative FullscreenTaskListener implementations do not use ShellInit
78         if (shellInit != null) {
79             shellInit.addInitCallback(this::onInit, this);
80         }
81     }
82 
onInit()83     private void onInit() {
84         mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FULLSCREEN);
85     }
86 
87     @Override
onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash)88     public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
89         if (mTasks.get(taskInfo.taskId) != null) {
90             throw new IllegalStateException("Task appeared more than once: #" + taskInfo.taskId);
91         }
92         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d",
93                 taskInfo.taskId);
94         final Point positionInParent = taskInfo.positionInParent;
95         final State state = new State();
96         state.mLeash = leash;
97         state.mTaskInfo = taskInfo;
98         mTasks.put(taskInfo.taskId, state);
99 
100         if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
101         updateRecentsForVisibleFullscreenTask(taskInfo);
102         boolean createdWindowDecor = false;
103         if (mWindowDecorViewModelOptional.isPresent()) {
104             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
105             createdWindowDecor = mWindowDecorViewModelOptional.get()
106                     .onTaskOpening(taskInfo, leash, t, t);
107             t.apply();
108         }
109         if (!createdWindowDecor) {
110             mSyncQueue.runInSync(t -> {
111                 if (!leash.isValid()) {
112                     // Task vanished before sync completion
113                     return;
114                 }
115                 // Reset several properties back to fullscreen (PiP, for example, leaves all these
116                 // properties in a bad state).
117                 t.setWindowCrop(leash, null);
118                 t.setPosition(leash, positionInParent.x, positionInParent.y);
119                 t.setAlpha(leash, 1f);
120                 t.setMatrix(leash, 1, 0, 0, 1);
121                 if (taskInfo.isVisible) {
122                     t.show(leash);
123                 }
124             });
125         }
126     }
127 
128     @Override
onTaskInfoChanged(RunningTaskInfo taskInfo)129     public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
130         final State state = mTasks.get(taskInfo.taskId);
131         final Point oldPositionInParent = state.mTaskInfo.positionInParent;
132         boolean oldVisible = state.mTaskInfo.isVisible;
133 
134         if (mWindowDecorViewModelOptional.isPresent()) {
135             mWindowDecorViewModelOptional.get().onTaskInfoChanged(taskInfo);
136         }
137         state.mTaskInfo = taskInfo;
138         if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
139         updateRecentsForVisibleFullscreenTask(taskInfo);
140 
141         final Point positionInParent = state.mTaskInfo.positionInParent;
142         boolean positionInParentChanged = !oldPositionInParent.equals(positionInParent);
143         boolean becameVisible = !oldVisible && state.mTaskInfo.isVisible;
144 
145         if (becameVisible || positionInParentChanged) {
146             mSyncQueue.runInSync(t -> {
147                 if (!state.mLeash.isValid()) {
148                     // Task vanished before sync completion
149                     return;
150                 }
151                 if (becameVisible) {
152                     t.show(state.mLeash);
153                 }
154                 t.setPosition(state.mLeash, positionInParent.x, positionInParent.y);
155             });
156         }
157     }
158 
159     @Override
onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)160     public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
161         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d",
162                 taskInfo.taskId);
163         mTasks.remove(taskInfo.taskId);
164         mWindowDecorViewModelOptional.ifPresent(v -> v.onTaskVanished(taskInfo));
165         if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
166         if (mWindowDecorViewModelOptional.isPresent()) {
167             mWindowDecorViewModelOptional.get().destroyWindowDecoration(taskInfo);
168         }
169     }
170 
updateRecentsForVisibleFullscreenTask(RunningTaskInfo taskInfo)171     private void updateRecentsForVisibleFullscreenTask(RunningTaskInfo taskInfo) {
172         mRecentTasksOptional.ifPresent(recentTasks -> {
173             if (taskInfo.isVisible) {
174                 // Remove any persisted splits if either tasks are now made fullscreen and visible
175                 recentTasks.removeSplitPair(taskInfo.taskId);
176             }
177         });
178     }
179 
180     @Override
attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b)181     public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
182         b.setParent(findTaskSurface(taskId));
183     }
184 
185     @Override
reparentChildSurfaceToTask(int taskId, SurfaceControl sc, SurfaceControl.Transaction t)186     public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
187             SurfaceControl.Transaction t) {
188         t.reparent(sc, findTaskSurface(taskId));
189     }
190 
findTaskSurface(int taskId)191     private SurfaceControl findTaskSurface(int taskId) {
192         if (!mTasks.contains(taskId)) {
193             throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
194         }
195         return mTasks.get(taskId).mLeash;
196     }
197 
198     @Override
dump(@onNull PrintWriter pw, String prefix)199     public void dump(@NonNull PrintWriter pw, String prefix) {
200         final String innerPrefix = prefix + "  ";
201         pw.println(prefix + this);
202         pw.println(innerPrefix + mTasks.size() + " Tasks");
203     }
204 
205     @Override
toString()206     public String toString() {
207         return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN);
208     }
209 }
210