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 android.window; 18 19 import static android.app.ActivityTaskManager.INVALID_TASK_ID; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 21 22 import android.app.ActivityManager; 23 import android.app.ActivityOptions; 24 import android.content.Context; 25 import android.graphics.Rect; 26 import android.util.Log; 27 import android.view.KeyEvent; 28 import android.view.SurfaceControl; 29 30 /** 31 * A component which handles embedded display of tasks within another window. The embedded task can 32 * be presented using the SurfaceControl provided from {@link #getSurfaceControl()}. 33 * 34 * @hide 35 */ 36 public class TaskOrganizerTaskEmbedder extends TaskEmbedder { 37 private static final String TAG = "TaskOrgTaskEmbedder"; 38 private static final boolean DEBUG = false; 39 40 private TaskOrganizer mTaskOrganizer; 41 private ActivityManager.RunningTaskInfo mTaskInfo; 42 private WindowContainerToken mTaskToken; 43 private SurfaceControl mTaskLeash; 44 private boolean mPendingNotifyBoundsChanged; 45 46 /** 47 * Constructs a new TaskEmbedder. 48 * 49 * @param context the context 50 * @param host the host for this embedded task 51 */ TaskOrganizerTaskEmbedder(Context context, TaskOrganizerTaskEmbedder.Host host)52 public TaskOrganizerTaskEmbedder(Context context, TaskOrganizerTaskEmbedder.Host host) { 53 super(context, host); 54 } 55 56 /** 57 * Whether this container has been initialized. 58 * 59 * @return true if initialized 60 */ 61 @Override isInitialized()62 public boolean isInitialized() { 63 return mTaskOrganizer != null; 64 } 65 66 @Override onInitialize()67 public boolean onInitialize() { 68 if (DEBUG) { 69 log("onInitialize"); 70 } 71 // Register the task organizer 72 mTaskOrganizer = new TaskOrganizerImpl(); 73 // TODO(wm-shell): This currently prevents other organizers from controlling MULT_WINDOW 74 // windowing mode tasks. Plan is to migrate this to a wm-shell front-end when that 75 // infrastructure is ready. 76 mTaskOrganizer.registerOrganizer(WINDOWING_MODE_MULTI_WINDOW); 77 mTaskOrganizer.setInterceptBackPressedOnTaskRoot(true); 78 79 return super.onInitialize(); 80 } 81 82 @Override onRelease()83 protected boolean onRelease() { 84 if (DEBUG) { 85 log("onRelease"); 86 } 87 if (!isInitialized()) { 88 return false; 89 } 90 mTaskOrganizer.unregisterOrganizer(); 91 resetTaskInfo(); 92 return true; 93 } 94 95 /** 96 * Starts presentation of tasks in this container. 97 */ 98 @Override start()99 public void start() { 100 super.start(); 101 if (DEBUG) { 102 log("start"); 103 } 104 if (!isInitialized()) { 105 return; 106 } 107 if (mTaskToken == null) { 108 return; 109 } 110 WindowContainerTransaction wct = new WindowContainerTransaction(); 111 wct.setHidden(mTaskToken, false /* hidden */); 112 WindowOrganizer.applyTransaction(wct); 113 // TODO(b/151449487): Only call callback once we enable synchronization 114 if (mListener != null) { 115 mListener.onTaskVisibilityChanged(getTaskId(), true); 116 } 117 } 118 119 /** 120 * Stops presentation of tasks in this container. 121 */ 122 @Override stop()123 public void stop() { 124 super.stop(); 125 if (DEBUG) { 126 log("stop"); 127 } 128 if (!isInitialized()) { 129 return; 130 } 131 if (mTaskToken == null) { 132 return; 133 } 134 WindowContainerTransaction wct = new WindowContainerTransaction(); 135 wct.setHidden(mTaskToken, true /* hidden */); 136 WindowOrganizer.applyTransaction(wct); 137 // TODO(b/151449487): Only call callback once we enable synchronization 138 if (mListener != null) { 139 mListener.onTaskVisibilityChanged(getTaskId(), false); 140 } 141 } 142 143 /** 144 * This should be called whenever the position or size of the surface changes 145 * or if touchable areas above the surface are added or removed. 146 */ 147 @Override notifyBoundsChanged()148 public void notifyBoundsChanged() { 149 super.notifyBoundsChanged(); 150 if (DEBUG) { 151 log("notifyBoundsChanged: screenBounds=" + mHost.getScreenBounds()); 152 } 153 if (mTaskToken == null) { 154 mPendingNotifyBoundsChanged = true; 155 return; 156 } 157 mPendingNotifyBoundsChanged = false; 158 159 // Update based on the screen bounds 160 Rect screenBounds = mHost.getScreenBounds(); 161 if (screenBounds.left < 0 || screenBounds.top < 0) { 162 screenBounds.offsetTo(0, 0); 163 } 164 165 WindowContainerTransaction wct = new WindowContainerTransaction(); 166 wct.setBounds(mTaskToken, screenBounds); 167 // TODO(b/151449487): Enable synchronization 168 WindowOrganizer.applyTransaction(wct); 169 } 170 171 /** 172 * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the 173 * virtual display. 174 */ 175 @Override performBackPress()176 public void performBackPress() { 177 // Do nothing, the task org task should already have focus if the caller is not focused 178 return; 179 } 180 181 /** An opaque unique identifier for this task surface among others being managed by the app. */ 182 @Override getId()183 public int getId() { 184 return getTaskId(); 185 } 186 187 /** 188 * Check if container is ready to launch and create {@link ActivityOptions} to target the 189 * virtual display. 190 * @param options The existing options to amend, or null if the caller wants new options to be 191 * created 192 */ 193 @Override prepareActivityOptions(ActivityOptions options)194 protected ActivityOptions prepareActivityOptions(ActivityOptions options) { 195 options = super.prepareActivityOptions(options); 196 options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW); 197 return options; 198 } 199 getTaskId()200 private int getTaskId() { 201 return mTaskInfo != null 202 ? mTaskInfo.taskId 203 : INVALID_TASK_ID; 204 } 205 resetTaskInfo()206 private void resetTaskInfo() { 207 if (DEBUG) { 208 log("resetTaskInfo"); 209 } 210 mTaskInfo = null; 211 mTaskToken = null; 212 mTaskLeash = null; 213 } 214 log(String msg)215 private void log(String msg) { 216 Log.d(TAG, "[" + System.identityHashCode(this) + "] " + msg); 217 } 218 219 private class TaskOrganizerImpl extends TaskOrganizer { 220 @Override onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)221 public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { 222 if (DEBUG) { 223 log("taskAppeared: " + taskInfo.taskId); 224 } 225 226 mTaskInfo = taskInfo; 227 mTaskToken = taskInfo.token; 228 mTaskLeash = leash; 229 mTransaction.reparent(mTaskLeash, mSurfaceControl) 230 .show(mTaskLeash) 231 .show(mSurfaceControl) 232 .apply(); 233 if (mPendingNotifyBoundsChanged) { 234 // TODO: Either defer show or hide and synchronize show with the resize 235 notifyBoundsChanged(); 236 } 237 mHost.post(() -> mHost.onTaskBackgroundColorChanged(TaskOrganizerTaskEmbedder.this, 238 taskInfo.taskDescription.getBackgroundColor())); 239 240 if (mListener != null) { 241 mListener.onTaskCreated(taskInfo.taskId, taskInfo.baseActivity); 242 } 243 } 244 245 @Override onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)246 public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { 247 mTaskInfo.taskDescription = taskInfo.taskDescription; 248 mHost.post(() -> mHost.onTaskBackgroundColorChanged(TaskOrganizerTaskEmbedder.this, 249 taskInfo.taskDescription.getBackgroundColor())); 250 } 251 252 @Override onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)253 public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { 254 if (DEBUG) { 255 log("taskVanished: " + taskInfo.taskId); 256 } 257 258 if (mTaskToken != null && (taskInfo == null 259 || mTaskToken.asBinder().equals(taskInfo.token.asBinder()))) { 260 if (mListener != null) { 261 mListener.onTaskRemovalStarted(taskInfo.taskId); 262 } 263 resetTaskInfo(); 264 } 265 } 266 267 @Override onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo)268 public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) { 269 if (mListener != null) { 270 mListener.onBackPressedOnTaskRoot(taskInfo.taskId); 271 } 272 } 273 } 274 } 275