• 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;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.app.ActivityManager;
24 import android.app.ActivityOptions;
25 import android.app.PendingIntent;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.pm.LauncherApps;
30 import android.content.pm.ShortcutInfo;
31 import android.graphics.Rect;
32 import android.graphics.Region;
33 import android.os.Binder;
34 import android.util.CloseGuard;
35 import android.view.SurfaceControl;
36 import android.view.SurfaceHolder;
37 import android.view.SurfaceView;
38 import android.view.View;
39 import android.view.ViewTreeObserver;
40 import android.window.WindowContainerToken;
41 import android.window.WindowContainerTransaction;
42 
43 import com.android.wm.shell.common.SyncTransactionQueue;
44 
45 import java.io.PrintWriter;
46 import java.util.concurrent.Executor;
47 
48 /**
49  * View that can display a task.
50  */
51 public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
52         ShellTaskOrganizer.TaskListener, ViewTreeObserver.OnComputeInternalInsetsListener {
53 
54     /** Callback for listening task state. */
55     public interface Listener {
56         /**
57          * Only called once when the surface has been created & the container is ready for
58          * launching activities.
59          */
onInitialized()60         default void onInitialized() {}
61 
62         /** Called when the container can no longer launch activities. */
onReleased()63         default void onReleased() {}
64 
65         /** Called when a task is created inside the container. */
onTaskCreated(int taskId, ComponentName name)66         default void onTaskCreated(int taskId, ComponentName name) {}
67 
68         /** Called when a task visibility changes. */
onTaskVisibilityChanged(int taskId, boolean visible)69         default void onTaskVisibilityChanged(int taskId, boolean visible) {}
70 
71         /** Called when a task is about to be removed from the stack inside the container. */
onTaskRemovalStarted(int taskId)72         default void onTaskRemovalStarted(int taskId) {}
73 
74         /** Called when a task is created inside the container. */
onBackPressedOnTaskRoot(int taskId)75         default void onBackPressedOnTaskRoot(int taskId) {}
76     }
77 
78     private final CloseGuard mGuard = new CloseGuard();
79 
80     private final ShellTaskOrganizer mTaskOrganizer;
81     private final Executor mShellExecutor;
82     private final SyncTransactionQueue mSyncQueue;
83     private final TaskViewTransitions mTaskViewTransitions;
84 
85     protected ActivityManager.RunningTaskInfo mTaskInfo;
86     private WindowContainerToken mTaskToken;
87     private SurfaceControl mTaskLeash;
88     private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
89     private boolean mSurfaceCreated;
90     private boolean mIsInitialized;
91     private boolean mNotifiedForInitialized;
92     private Listener mListener;
93     private Executor mListenerExecutor;
94     private Region mObscuredTouchRegion;
95 
96     private final Rect mTmpRect = new Rect();
97     private final Rect mTmpRootRect = new Rect();
98     private final int[] mTmpLocation = new int[2];
99 
TaskView(Context context, ShellTaskOrganizer organizer, TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue)100     public TaskView(Context context, ShellTaskOrganizer organizer,
101             TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
102         super(context, null, 0, 0, true /* disableBackgroundLayer */);
103 
104         mTaskOrganizer = organizer;
105         mShellExecutor = organizer.getExecutor();
106         mSyncQueue = syncQueue;
107         mTaskViewTransitions = taskViewTransitions;
108         if (mTaskViewTransitions != null) {
109             mTaskViewTransitions.addTaskView(this);
110         }
111         setUseAlpha();
112         getHolder().addCallback(this);
113         mGuard.open("release");
114     }
115 
116     /**
117      * @return {@code True} when the TaskView's surface has been created, {@code False} otherwise.
118      */
isInitialized()119     public boolean isInitialized() {
120         return mIsInitialized;
121     }
122 
123     /** Until all users are converted, we may have mixed-use (eg. Car). */
isUsingShellTransitions()124     private boolean isUsingShellTransitions() {
125         return mTaskViewTransitions != null && mTaskViewTransitions.isEnabled();
126     }
127 
128     /**
129      * Only one listener may be set on the view, throws an exception otherwise.
130      */
setListener(@onNull Executor executor, Listener listener)131     public void setListener(@NonNull Executor executor, Listener listener) {
132         if (mListener != null) {
133             throw new IllegalStateException(
134                     "Trying to set a listener when one has already been set");
135         }
136         mListener = listener;
137         mListenerExecutor = executor;
138     }
139 
140     /**
141      * Launch an activity represented by {@link ShortcutInfo}.
142      * <p>The owner of this container must be allowed to access the shortcut information,
143      * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method.
144      *
145      * @param shortcut the shortcut used to launch the activity.
146      * @param options options for the activity.
147      * @param launchBounds the bounds (window size and position) that the activity should be
148      *                     launched in, in pixels and in screen coordinates.
149      */
startShortcutActivity(@onNull ShortcutInfo shortcut, @NonNull ActivityOptions options, @Nullable Rect launchBounds)150     public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
151             @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
152         prepareActivityOptions(options, launchBounds);
153         LauncherApps service = mContext.getSystemService(LauncherApps.class);
154         if (isUsingShellTransitions()) {
155             mShellExecutor.execute(() -> {
156                 final WindowContainerTransaction wct = new WindowContainerTransaction();
157                 wct.startShortcut(mContext.getPackageName(), shortcut, options.toBundle());
158                 mTaskViewTransitions.startTaskView(wct, this);
159             });
160             return;
161         }
162         try {
163             service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle());
164         } catch (Exception e) {
165             throw new RuntimeException(e);
166         }
167     }
168 
169     /**
170      * Launch a new activity.
171      *
172      * @param pendingIntent Intent used to launch an activity.
173      * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
174      * @param options options for the activity.
175      * @param launchBounds the bounds (window size and position) that the activity should be
176      *                     launched in, in pixels and in screen coordinates.
177      */
startActivity(@onNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, @NonNull ActivityOptions options, @Nullable Rect launchBounds)178     public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
179             @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
180         prepareActivityOptions(options, launchBounds);
181         if (isUsingShellTransitions()) {
182             mShellExecutor.execute(() -> {
183                 WindowContainerTransaction wct = new WindowContainerTransaction();
184                 wct.sendPendingIntent(pendingIntent, fillInIntent, options.toBundle());
185                 mTaskViewTransitions.startTaskView(wct, this);
186             });
187             return;
188         }
189         try {
190             pendingIntent.send(mContext, 0 /* code */, fillInIntent,
191                     null /* onFinished */, null /* handler */, null /* requiredPermission */,
192                     options.toBundle());
193         } catch (Exception e) {
194             throw new RuntimeException(e);
195         }
196     }
197 
prepareActivityOptions(ActivityOptions options, Rect launchBounds)198     private void prepareActivityOptions(ActivityOptions options, Rect launchBounds) {
199         final Binder launchCookie = new Binder();
200         mShellExecutor.execute(() -> {
201             mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, this);
202         });
203         options.setLaunchBounds(launchBounds);
204         options.setLaunchCookie(launchCookie);
205         options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
206         options.setRemoveWithTaskOrganizer(true);
207     }
208 
209     /**
210      * Indicates a region of the view that is not touchable.
211      *
212      * @param obscuredRect the obscured region of the view.
213      */
setObscuredTouchRect(Rect obscuredRect)214     public void setObscuredTouchRect(Rect obscuredRect) {
215         mObscuredTouchRegion = obscuredRect != null ? new Region(obscuredRect) : null;
216     }
217 
218     /**
219      * Indicates a region of the view that is not touchable.
220      *
221      * @param obscuredRegion the obscured region of the view.
222      */
setObscuredTouchRegion(Region obscuredRegion)223     public void setObscuredTouchRegion(Region obscuredRegion) {
224         mObscuredTouchRegion = obscuredRegion;
225     }
226 
227     /**
228      * Call when view position or size has changed. Do not call when animating.
229      */
onLocationChanged()230     public void onLocationChanged() {
231         if (mTaskToken == null) {
232             return;
233         }
234         // Sync Transactions can't operate simultaneously with shell transition collection.
235         // The transition animation (upon showing) will sync the location itself.
236         if (isUsingShellTransitions() && mTaskViewTransitions.hasPending()) return;
237 
238         WindowContainerTransaction wct = new WindowContainerTransaction();
239         updateWindowBounds(wct);
240         mSyncQueue.queue(wct);
241     }
242 
updateWindowBounds(WindowContainerTransaction wct)243     private void updateWindowBounds(WindowContainerTransaction wct) {
244         getBoundsOnScreen(mTmpRect);
245         wct.setBounds(mTaskToken, mTmpRect);
246     }
247 
248     /**
249      * Release this container if it is initialized.
250      */
release()251     public void release() {
252         performRelease();
253     }
254 
255     @Override
finalize()256     protected void finalize() throws Throwable {
257         try {
258             if (mGuard != null) {
259                 mGuard.warnIfOpen();
260                 performRelease();
261             }
262         } finally {
263             super.finalize();
264         }
265     }
266 
performRelease()267     private void performRelease() {
268         getHolder().removeCallback(this);
269         if (mTaskViewTransitions != null) {
270             mTaskViewTransitions.removeTaskView(this);
271         }
272         mShellExecutor.execute(() -> {
273             mTaskOrganizer.removeListener(this);
274             resetTaskInfo();
275         });
276         mGuard.close();
277         mIsInitialized = false;
278         notifyReleased();
279     }
280 
281     /** Called when the {@link TaskView} has been released. */
notifyReleased()282     protected void notifyReleased() {
283         if (mListener != null && mNotifiedForInitialized) {
284             mListenerExecutor.execute(() -> {
285                 mListener.onReleased();
286             });
287             mNotifiedForInitialized = false;
288         }
289     }
290 
resetTaskInfo()291     private void resetTaskInfo() {
292         mTaskInfo = null;
293         mTaskToken = null;
294         mTaskLeash = null;
295     }
296 
updateTaskVisibility()297     private void updateTaskVisibility() {
298         WindowContainerTransaction wct = new WindowContainerTransaction();
299         wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */);
300         mSyncQueue.queue(wct);
301         if (mListener == null) {
302             return;
303         }
304         int taskId = mTaskInfo.taskId;
305         mSyncQueue.runInSync((t) -> {
306             mListenerExecutor.execute(() -> {
307                 mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated);
308             });
309         });
310     }
311 
312     @Override
onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)313     public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
314             SurfaceControl leash) {
315         if (isUsingShellTransitions()) {
316             // Everything else handled by enter transition.
317             return;
318         }
319         mTaskInfo = taskInfo;
320         mTaskToken = taskInfo.token;
321         mTaskLeash = leash;
322 
323         if (mSurfaceCreated) {
324             // Surface is ready, so just reparent the task to this surface control
325             mTransaction.reparent(mTaskLeash, getSurfaceControl())
326                     .show(mTaskLeash)
327                     .apply();
328         } else {
329             // The surface has already been destroyed before the task has appeared,
330             // so go ahead and hide the task entirely
331             updateTaskVisibility();
332         }
333         mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true);
334         onLocationChanged();
335         if (taskInfo.taskDescription != null) {
336             int backgroundColor = taskInfo.taskDescription.getBackgroundColor();
337             mSyncQueue.runInSync((t) -> {
338                 setResizeBackgroundColor(t, backgroundColor);
339             });
340         }
341 
342         if (mListener != null) {
343             final int taskId = taskInfo.taskId;
344             final ComponentName baseActivity = taskInfo.baseActivity;
345             mListenerExecutor.execute(() -> {
346                 mListener.onTaskCreated(taskId, baseActivity);
347             });
348         }
349     }
350 
351     @Override
onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)352     public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
353         // Unlike Appeared, we can't yet guarantee that vanish will happen within a transition that
354         // we know about -- so leave clean-up here even if shell transitions are enabled.
355         if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
356 
357         if (mListener != null) {
358             final int taskId = taskInfo.taskId;
359             mListenerExecutor.execute(() -> {
360                 mListener.onTaskRemovalStarted(taskId);
361             });
362         }
363         mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
364 
365         // Unparent the task when this surface is destroyed
366         mTransaction.reparent(mTaskLeash, null).apply();
367         resetTaskInfo();
368     }
369 
370     @Override
onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)371     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
372         if (taskInfo.taskDescription != null) {
373             setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
374         }
375     }
376 
377     @Override
onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo)378     public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
379         if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
380         if (mListener != null) {
381             final int taskId = taskInfo.taskId;
382             mListenerExecutor.execute(() -> {
383                 mListener.onBackPressedOnTaskRoot(taskId);
384             });
385         }
386     }
387 
388     @Override
attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b)389     public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
390         b.setParent(findTaskSurface(taskId));
391     }
392 
393     @Override
reparentChildSurfaceToTask(int taskId, SurfaceControl sc, SurfaceControl.Transaction t)394     public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
395             SurfaceControl.Transaction t) {
396         t.reparent(sc, findTaskSurface(taskId));
397     }
398 
findTaskSurface(int taskId)399     private SurfaceControl findTaskSurface(int taskId) {
400         if (mTaskInfo == null || mTaskLeash == null || mTaskInfo.taskId != taskId) {
401             throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
402         }
403         return mTaskLeash;
404     }
405 
406     @Override
dump(@ndroidx.annotation.NonNull PrintWriter pw, String prefix)407     public void dump(@androidx.annotation.NonNull PrintWriter pw, String prefix) {
408         final String innerPrefix = prefix + "  ";
409         final String childPrefix = innerPrefix + "  ";
410         pw.println(prefix + this);
411     }
412 
413     @Override
toString()414     public String toString() {
415         return "TaskView" + ":" + (mTaskInfo != null ? mTaskInfo.taskId : "null");
416     }
417 
418     @Override
surfaceCreated(SurfaceHolder holder)419     public void surfaceCreated(SurfaceHolder holder) {
420         mSurfaceCreated = true;
421         mIsInitialized = true;
422         notifyInitialized();
423         mShellExecutor.execute(() -> {
424             if (mTaskToken == null) {
425                 // Nothing to update, task is not yet available
426                 return;
427             }
428             if (isUsingShellTransitions()) {
429                 mTaskViewTransitions.setTaskViewVisible(this, true /* visible */);
430                 return;
431             }
432             // Reparent the task when this surface is created
433             mTransaction.reparent(mTaskLeash, getSurfaceControl())
434                     .show(mTaskLeash)
435                     .apply();
436             updateTaskVisibility();
437         });
438     }
439 
440     /** Called when the {@link TaskView} is initialized. */
notifyInitialized()441     protected void notifyInitialized() {
442         if (mListener != null && !mNotifiedForInitialized) {
443             mNotifiedForInitialized = true;
444             mListenerExecutor.execute(() -> {
445                 mListener.onInitialized();
446             });
447         }
448     }
449 
450     @Override
surfaceChanged(SurfaceHolder holder, int format, int width, int height)451     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
452         if (mTaskToken == null) {
453             return;
454         }
455         onLocationChanged();
456     }
457 
458     @Override
surfaceDestroyed(SurfaceHolder holder)459     public void surfaceDestroyed(SurfaceHolder holder) {
460         mSurfaceCreated = false;
461         mShellExecutor.execute(() -> {
462             if (mTaskToken == null) {
463                 // Nothing to update, task is not yet available
464                 return;
465             }
466 
467             if (isUsingShellTransitions()) {
468                 mTaskViewTransitions.setTaskViewVisible(this, false /* visible */);
469                 return;
470             }
471 
472             // Unparent the task when this surface is destroyed
473             mTransaction.reparent(mTaskLeash, null).apply();
474             updateTaskVisibility();
475         });
476     }
477 
478     @Override
onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo)479     public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
480         // TODO(b/176854108): Consider to move the logic into gatherTransparentRegions since this
481         //   is dependent on the order of listener.
482         // If there are multiple TaskViews, we'll set the touchable area as the root-view, then
483         // subtract each TaskView from it.
484         if (inoutInfo.touchableRegion.isEmpty()) {
485             inoutInfo.setTouchableInsets(
486                     ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
487             View root = getRootView();
488             root.getLocationInWindow(mTmpLocation);
489             mTmpRootRect.set(mTmpLocation[0], mTmpLocation[1], root.getWidth(), root.getHeight());
490             inoutInfo.touchableRegion.set(mTmpRootRect);
491         }
492         getLocationInWindow(mTmpLocation);
493         mTmpRect.set(mTmpLocation[0], mTmpLocation[1],
494                 mTmpLocation[0] + getWidth(), mTmpLocation[1] + getHeight());
495         inoutInfo.touchableRegion.op(mTmpRect, Region.Op.DIFFERENCE);
496 
497         if (mObscuredTouchRegion != null) {
498             inoutInfo.touchableRegion.op(mObscuredTouchRegion, Region.Op.UNION);
499         }
500     }
501 
502     @Override
onAttachedToWindow()503     protected void onAttachedToWindow() {
504         super.onAttachedToWindow();
505         getViewTreeObserver().addOnComputeInternalInsetsListener(this);
506     }
507 
508     @Override
onDetachedFromWindow()509     protected void onDetachedFromWindow() {
510         super.onDetachedFromWindow();
511         getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
512     }
513 
514     /** Returns the task info for the task in the TaskView. */
515     @Nullable
getTaskInfo()516     public ActivityManager.RunningTaskInfo getTaskInfo() {
517         return mTaskInfo;
518     }
519 
prepareHideAnimation(@onNull SurfaceControl.Transaction finishTransaction)520     void prepareHideAnimation(@NonNull SurfaceControl.Transaction finishTransaction) {
521         if (mTaskToken == null) {
522             // Nothing to update, task is not yet available
523             return;
524         }
525 
526         finishTransaction.reparent(mTaskLeash, null).apply();
527 
528         if (mListener != null) {
529             final int taskId = mTaskInfo.taskId;
530             mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */);
531         }
532     }
533 
534     /**
535      * Called when the associated Task closes. If the TaskView is just being hidden, prepareHide
536      * is used instead.
537      */
prepareCloseAnimation()538     void prepareCloseAnimation() {
539         if (mTaskToken != null) {
540             if (mListener != null) {
541                 final int taskId = mTaskInfo.taskId;
542                 mListenerExecutor.execute(() -> {
543                     mListener.onTaskRemovalStarted(taskId);
544                 });
545             }
546             mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
547         }
548         resetTaskInfo();
549     }
550 
prepareOpenAnimation(final boolean newTask, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, WindowContainerTransaction wct)551     void prepareOpenAnimation(final boolean newTask,
552             @NonNull SurfaceControl.Transaction startTransaction,
553             @NonNull SurfaceControl.Transaction finishTransaction,
554             ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
555             WindowContainerTransaction wct) {
556         mTaskInfo = taskInfo;
557         mTaskToken = mTaskInfo.token;
558         mTaskLeash = leash;
559         if (mSurfaceCreated) {
560             // Surface is ready, so just reparent the task to this surface control
561             startTransaction.reparent(mTaskLeash, getSurfaceControl())
562                     .show(mTaskLeash)
563                     .apply();
564             // Also reparent on finishTransaction since the finishTransaction will reparent back
565             // to its "original" parent by default.
566             finishTransaction.reparent(mTaskLeash, getSurfaceControl())
567                     .setPosition(mTaskLeash, 0, 0)
568                     .apply();
569 
570             // TODO: determine if this is really necessary or not
571             updateWindowBounds(wct);
572         } else {
573             // The surface has already been destroyed before the task has appeared,
574             // so go ahead and hide the task entirely
575             wct.setHidden(mTaskToken, true /* hidden */);
576             // listener callback is below
577         }
578         if (newTask) {
579             mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true /* intercept */);
580         }
581 
582         if (mTaskInfo.taskDescription != null) {
583             int backgroundColor = mTaskInfo.taskDescription.getBackgroundColor();
584             setResizeBackgroundColor(startTransaction, backgroundColor);
585         }
586 
587         if (mListener != null) {
588             final int taskId = mTaskInfo.taskId;
589             final ComponentName baseActivity = mTaskInfo.baseActivity;
590 
591             mListenerExecutor.execute(() -> {
592                 if (newTask) {
593                     mListener.onTaskCreated(taskId, baseActivity);
594                 }
595                 // Even if newTask, send a visibilityChange if the surface was destroyed.
596                 if (!newTask || !mSurfaceCreated) {
597                     mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */);
598                 }
599             });
600         }
601     }
602 }
603