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