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