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.taskview; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.ActivityManager; 22 import android.app.ActivityOptions; 23 import android.app.PendingIntent; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.LauncherApps; 28 import android.content.pm.ShortcutInfo; 29 import android.graphics.Insets; 30 import android.graphics.Rect; 31 import android.graphics.Region; 32 import android.view.SurfaceControl; 33 import android.view.SurfaceHolder; 34 import android.view.SurfaceView; 35 import android.view.View; 36 import android.view.ViewTreeObserver; 37 38 import java.util.concurrent.Executor; 39 40 /** 41 * A {@link SurfaceView} that can display a task. This is a concrete implementation for 42 * {@link TaskViewBase} which interacts {@link TaskViewTaskController}. 43 */ 44 public class TaskView extends SurfaceView implements SurfaceHolder.Callback, 45 ViewTreeObserver.OnComputeInternalInsetsListener, TaskViewBase { 46 /** Callback for listening task state. */ 47 public interface Listener { 48 /** 49 * Only called once when the surface has been created & the container is ready for 50 * launching activities. 51 */ onInitialized()52 default void onInitialized() {} 53 54 /** Called when the container can no longer launch activities. */ onReleased()55 default void onReleased() {} 56 57 /** Called when a task is created inside the container. */ onTaskCreated(int taskId, ComponentName name)58 default void onTaskCreated(int taskId, ComponentName name) {} 59 60 /** Called when a task visibility changes. */ onTaskVisibilityChanged(int taskId, boolean visible)61 default void onTaskVisibilityChanged(int taskId, boolean visible) {} 62 63 /** Called when a task is about to be removed from the stack inside the container. */ onTaskRemovalStarted(int taskId)64 default void onTaskRemovalStarted(int taskId) {} 65 66 /** Called when a task is created inside the container. */ onBackPressedOnTaskRoot(int taskId)67 default void onBackPressedOnTaskRoot(int taskId) {} 68 } 69 70 private final Rect mTmpRect = new Rect(); 71 private final Rect mTmpRootRect = new Rect(); 72 private final int[] mTmpLocation = new int[2]; 73 private final Rect mBoundsOnScreen = new Rect(); 74 private final TaskViewTaskController mTaskViewTaskController; 75 private Region mObscuredTouchRegion; 76 private Insets mCaptionInsets; 77 TaskView(Context context, TaskViewTaskController taskViewTaskController)78 public TaskView(Context context, TaskViewTaskController taskViewTaskController) { 79 super(context, null, 0, 0, true /* disableBackgroundLayer */); 80 mTaskViewTaskController = taskViewTaskController; 81 // TODO(b/266736992): Think about a better way to set the TaskViewBase on the 82 // TaskViewTaskController and vice-versa 83 mTaskViewTaskController.setTaskViewBase(this); 84 getHolder().addCallback(this); 85 } 86 87 /** 88 * Launch a new activity. 89 * 90 * @param pendingIntent Intent used to launch an activity. 91 * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()} 92 * @param options options for the activity. 93 * @param launchBounds the bounds (window size and position) that the activity should be 94 * launched in, in pixels and in screen coordinates. 95 */ startActivity(@onNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, @NonNull ActivityOptions options, @Nullable Rect launchBounds)96 public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, 97 @NonNull ActivityOptions options, @Nullable Rect launchBounds) { 98 mTaskViewTaskController.startActivity(pendingIntent, fillInIntent, options, launchBounds); 99 } 100 101 /** 102 * Launch an activity represented by {@link ShortcutInfo}. 103 * <p>The owner of this container must be allowed to access the shortcut information, 104 * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method. 105 * 106 * @param shortcut the shortcut used to launch the activity. 107 * @param options options for the activity. 108 * @param launchBounds the bounds (window size and position) that the activity should be 109 * launched in, in pixels and in screen coordinates. 110 */ startShortcutActivity(@onNull ShortcutInfo shortcut, @NonNull ActivityOptions options, @Nullable Rect launchBounds)111 public void startShortcutActivity(@NonNull ShortcutInfo shortcut, 112 @NonNull ActivityOptions options, @Nullable Rect launchBounds) { 113 mTaskViewTaskController.startShortcutActivity(shortcut, options, launchBounds); 114 } 115 116 @Override onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)117 public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { 118 onLocationChanged(); 119 if (taskInfo.taskDescription != null) { 120 setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor()); 121 } 122 } 123 124 @Override onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)125 public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { 126 if (taskInfo.taskDescription != null) { 127 setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor()); 128 } 129 } 130 131 /** 132 * @return {@code True} when the TaskView's surface has been created, {@code False} otherwise. 133 */ isInitialized()134 public boolean isInitialized() { 135 return mTaskViewTaskController.isInitialized(); 136 } 137 138 @Override getCurrentBoundsOnScreen()139 public Rect getCurrentBoundsOnScreen() { 140 getBoundsOnScreen(mTmpRect); 141 return mTmpRect; 142 } 143 144 @Override setResizeBgColor(SurfaceControl.Transaction t, int bgColor)145 public void setResizeBgColor(SurfaceControl.Transaction t, int bgColor) { 146 setResizeBackgroundColor(t, bgColor); 147 } 148 149 /** 150 * Only one listener may be set on the view, throws an exception otherwise. 151 */ setListener(@onNull Executor executor, TaskView.Listener listener)152 public void setListener(@NonNull Executor executor, TaskView.Listener listener) { 153 mTaskViewTaskController.setListener(executor, listener); 154 } 155 156 /** 157 * Indicates a region of the view that is not touchable. 158 * 159 * @param obscuredRect the obscured region of the view. 160 */ setObscuredTouchRect(Rect obscuredRect)161 public void setObscuredTouchRect(Rect obscuredRect) { 162 mObscuredTouchRegion = obscuredRect != null ? new Region(obscuredRect) : null; 163 } 164 165 /** 166 * Indicates a region of the view that is not touchable. 167 * 168 * @param obscuredRegion the obscured region of the view. 169 */ setObscuredTouchRegion(Region obscuredRegion)170 public void setObscuredTouchRegion(Region obscuredRegion) { 171 mObscuredTouchRegion = obscuredRegion; 172 } 173 174 /** 175 * Sets a region of the task to inset to allow for a caption bar. Currently only top insets 176 * are supported. 177 * <p> 178 * This region will be factored in as an area of taskview that is not touchable activity 179 * content (i.e. you don't need to additionally set {@link #setObscuredTouchRect(Rect)} for 180 * the caption area). 181 * 182 * @param captionInsets the insets to apply to task view. 183 */ setCaptionInsets(Insets captionInsets)184 public void setCaptionInsets(Insets captionInsets) { 185 mCaptionInsets = captionInsets; 186 if (captionInsets == null) { 187 // If captions are null we can set them now; otherwise they'll get set in 188 // onComputeInternalInsets. 189 mTaskViewTaskController.setCaptionInsets(null); 190 } 191 } 192 193 /** 194 * Call when view position or size has changed. Do not call when animating. 195 */ onLocationChanged()196 public void onLocationChanged() { 197 getBoundsOnScreen(mTmpRect); 198 mTaskViewTaskController.setWindowBounds(mTmpRect); 199 } 200 201 /** 202 * Call to remove the task from window manager. This task will not appear in recents. 203 */ removeTask()204 public void removeTask() { 205 mTaskViewTaskController.removeTask(); 206 } 207 208 /** 209 * Release this container if it is initialized. 210 */ release()211 public void release() { 212 getHolder().removeCallback(this); 213 mTaskViewTaskController.release(); 214 } 215 216 @Override toString()217 public String toString() { 218 return mTaskViewTaskController.toString(); 219 } 220 221 @Override surfaceCreated(SurfaceHolder holder)222 public void surfaceCreated(SurfaceHolder holder) { 223 mTaskViewTaskController.surfaceCreated(getSurfaceControl()); 224 } 225 226 @Override surfaceChanged(@ndroidx.annotation.NonNull SurfaceHolder holder, int format, int width, int height)227 public void surfaceChanged(@androidx.annotation.NonNull SurfaceHolder holder, int format, 228 int width, int height) { 229 getBoundsOnScreen(mTmpRect); 230 mTaskViewTaskController.setWindowBounds(mTmpRect); 231 } 232 233 @Override surfaceDestroyed(SurfaceHolder holder)234 public void surfaceDestroyed(SurfaceHolder holder) { 235 mTaskViewTaskController.surfaceDestroyed(); 236 } 237 238 @Override onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo)239 public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) { 240 // TODO(b/176854108): Consider to move the logic into gatherTransparentRegions since this 241 // is dependent on the order of listener. 242 // If there are multiple TaskViews, we'll set the touchable area as the root-view, then 243 // subtract each TaskView from it. 244 if (inoutInfo.touchableRegion.isEmpty()) { 245 inoutInfo.setTouchableInsets( 246 ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); 247 View root = getRootView(); 248 root.getLocationInWindow(mTmpLocation); 249 mTmpRootRect.set(mTmpLocation[0], mTmpLocation[1], root.getWidth(), root.getHeight()); 250 inoutInfo.touchableRegion.set(mTmpRootRect); 251 } 252 getLocationInWindow(mTmpLocation); 253 mTmpRect.set(mTmpLocation[0], mTmpLocation[1], 254 mTmpLocation[0] + getWidth(), mTmpLocation[1] + getHeight()); 255 if (mCaptionInsets != null) { 256 mTmpRect.inset(mCaptionInsets); 257 getBoundsOnScreen(mBoundsOnScreen); 258 mTaskViewTaskController.setCaptionInsets(new Rect( 259 mBoundsOnScreen.left, 260 mBoundsOnScreen.top, 261 mBoundsOnScreen.right + getWidth(), 262 mBoundsOnScreen.top + mCaptionInsets.top)); 263 } 264 inoutInfo.touchableRegion.op(mTmpRect, Region.Op.DIFFERENCE); 265 266 if (mObscuredTouchRegion != null) { 267 inoutInfo.touchableRegion.op(mObscuredTouchRegion, Region.Op.UNION); 268 } 269 } 270 271 @Override onAttachedToWindow()272 protected void onAttachedToWindow() { 273 super.onAttachedToWindow(); 274 getViewTreeObserver().addOnComputeInternalInsetsListener(this); 275 } 276 277 @Override onDetachedFromWindow()278 protected void onDetachedFromWindow() { 279 super.onDetachedFromWindow(); 280 getViewTreeObserver().removeOnComputeInternalInsetsListener(this); 281 } 282 283 /** Returns the task info for the task in the TaskView. */ 284 @Nullable getTaskInfo()285 public ActivityManager.RunningTaskInfo getTaskInfo() { 286 return mTaskViewTaskController.getTaskInfo(); 287 } 288 } 289