• 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 android.window;
18 
19 import static android.view.Display.INVALID_DISPLAY;
20 
21 import android.annotation.CallSuper;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.app.ActivityOptions;
25 import android.app.ActivityTaskManager;
26 import android.app.IActivityTaskManager;
27 import android.app.PendingIntent;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.pm.LauncherApps;
32 import android.content.pm.ShortcutInfo;
33 import android.graphics.Insets;
34 import android.graphics.Matrix;
35 import android.graphics.Point;
36 import android.graphics.Rect;
37 import android.graphics.Region;
38 import android.hardware.display.VirtualDisplay;
39 import android.os.RemoteException;
40 import android.os.UserHandle;
41 import android.view.IWindow;
42 import android.view.IWindowManager;
43 import android.view.IWindowSession;
44 import android.view.KeyEvent;
45 import android.view.SurfaceControl;
46 import android.view.WindowManagerGlobal;
47 
48 import dalvik.system.CloseGuard;
49 
50 /**
51  * A component which handles embedded display of tasks within another window. The embedded task can
52  * be presented using the SurfaceControl provided from {@link #getSurfaceControl()}.
53  *
54  * @hide
55  */
56 public abstract class TaskEmbedder {
57     private static final String TAG = "TaskEmbedder";
58 
59     /**
60      * A component which will host the task.
61      */
62     public interface Host {
63         /** @return the screen area where touches should be dispatched to the embedded Task */
getTapExcludeRegion()64         Region getTapExcludeRegion();
65 
66         /** @return a matrix which transforms from screen-space to the embedded task surface */
getScreenToTaskMatrix()67         Matrix getScreenToTaskMatrix();
68 
69         /** @return the window containing the parent surface, if attached and available */
getWindow()70         @Nullable IWindow getWindow();
71 
72         /** @return the x/y offset from the origin of the window to the surface */
getPositionInWindow()73         Point getPositionInWindow();
74 
75         /** @return the screen bounds of the host */
getScreenBounds()76         Rect getScreenBounds();
77 
78         /** @return whether this surface is able to receive pointer events */
canReceivePointerEvents()79         boolean canReceivePointerEvents();
80 
81         /** @return the width of the container for the embedded task */
getWidth()82         int getWidth();
83 
84         /** @return the height of the container for the embedded task */
getHeight()85         int getHeight();
86 
87         /**
88          * Called to inform the host of the task's background color. This can be used to
89          * fill unpainted areas if necessary.
90          */
onTaskBackgroundColorChanged(TaskEmbedder ts, int bgColor)91         void onTaskBackgroundColorChanged(TaskEmbedder ts, int bgColor);
92 
93         /**
94          * Posts a runnable to be run on the host's handler.
95          */
post(Runnable r)96         boolean post(Runnable r);
97     }
98 
99     /**
100      * Describes changes to the state of the TaskEmbedder as well the tasks within.
101      */
102     public interface Listener {
103         /** Called when the container is ready for launching activities. */
onInitialized()104         default void onInitialized() {}
105 
106         /** Called when the container can no longer launch activities. */
onReleased()107         default void onReleased() {}
108 
109         /** Called when a task is created inside the container. */
onTaskCreated(int taskId, ComponentName name)110         default void onTaskCreated(int taskId, ComponentName name) {}
111 
112         /** Called when a task visibility changes. */
onTaskVisibilityChanged(int taskId, boolean visible)113         default void onTaskVisibilityChanged(int taskId, boolean visible) {}
114 
115         /** Called when a task is moved to the front of the stack inside the container. */
onTaskMovedToFront(int taskId)116         default void onTaskMovedToFront(int taskId) {}
117 
118         /** Called when a task is about to be removed from the stack inside the container. */
onTaskRemovalStarted(int taskId)119         default void onTaskRemovalStarted(int taskId) {}
120 
121         /** Called when a task is created inside the container. */
onBackPressedOnTaskRoot(int taskId)122         default void onBackPressedOnTaskRoot(int taskId) {}
123     }
124 
125     protected IActivityTaskManager mActivityTaskManager = ActivityTaskManager.getService();
126 
127     protected final Context mContext;
128     protected TaskEmbedder.Host mHost;
129 
130     protected SurfaceControl.Transaction mTransaction;
131     protected SurfaceControl mSurfaceControl;
132     protected Listener mListener;
133     protected boolean mOpened; // Protected by mGuard.
134 
135     private final CloseGuard mGuard = CloseGuard.get();
136 
137 
138     /**
139      * Constructs a new TaskEmbedder.
140      *
141      * @param context the context
142      * @param host the host for this embedded task
143      */
TaskEmbedder(Context context, TaskEmbedder.Host host)144     public TaskEmbedder(Context context, TaskEmbedder.Host host) {
145         mContext = context;
146         mHost = host;
147     }
148 
149     /**
150      * Initialize this container when the ActivityView's SurfaceView is first created.
151      *
152      * @param parent the surface control for the parent surface
153      * @return true if initialized successfully
154      */
initialize(SurfaceControl parent)155     public boolean initialize(SurfaceControl parent) {
156         if (isInitialized()) {
157             throw new IllegalStateException("Trying to initialize for the second time.");
158         }
159 
160         mTransaction = new SurfaceControl.Transaction();
161         // Create a container surface to which the task content will be reparented
162         final String name = "TaskEmbedder - " + Integer.toHexString(System.identityHashCode(this));
163         mSurfaceControl = new SurfaceControl.Builder()
164                 .setContainerLayer()
165                 .setParent(parent)
166                 .setName(name)
167                 .setCallsite("TaskEmbedder.initialize")
168                 .build();
169 
170         if (!onInitialize()) {
171             return false;
172         }
173         if (mListener != null && isInitialized()) {
174             mListener.onInitialized();
175         }
176         mOpened = true;
177         mGuard.open("release");
178         mTransaction.show(getSurfaceControl()).apply();
179         return true;
180     }
181 
182     /**
183      * Whether this container has been initialized.
184      *
185      * @return true if initialized
186      */
isInitialized()187     public abstract boolean isInitialized();
188 
189     /**
190      * Called when the task embedder should be initialized.
191      * NOTE: all overriding methods should call this one after they finish their initialization.
192      * @return whether to report whether the embedder was initialized.
193      */
onInitialize()194     public boolean onInitialize() {
195         updateLocationAndTapExcludeRegion();
196         return true;
197     }
198 
199     /**
200      * Called when the task embedder should be released.
201      * @return whether to report whether the embedder was released.
202      */
onRelease()203     protected boolean onRelease() {
204         // Clear tap-exclude region (if any) for this window.
205         clearTapExcludeRegion();
206         return true;
207     }
208 
209     /**
210      * Starts presentation of tasks in this container.
211      */
start()212     public void start() {
213         updateLocationAndTapExcludeRegion();
214     }
215 
216     /**
217      * Stops presentation of tasks in this container.
218      */
stop()219     public void stop() {
220         clearTapExcludeRegion();
221     }
222 
223     /**
224      * This should be called whenever the position or size of the surface changes
225      * or if touchable areas above the surface are added or removed.
226      */
notifyBoundsChanged()227     public void notifyBoundsChanged() {
228         updateLocationAndTapExcludeRegion();
229     }
230 
231     /**
232      * Called to update the dimensions whenever the host size changes.
233      *
234      * @param width the new width of the surface
235      * @param height the new height of the surface
236      */
resizeTask(int width, int height)237     public void resizeTask(int width, int height) {
238         // Do nothing
239     }
240 
241     /**
242      * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the
243      * virtual display.
244      */
performBackPress()245     public abstract void performBackPress();
246 
247     /**
248      * An opaque unique identifier for this task surface among others being managed by the app.
249      */
getId()250     public abstract int getId();
251 
252     /**
253      * Calculates and updates the {@param region} with the transparent region for this task
254      * embedder.
255      */
gatherTransparentRegion(Region region)256     public boolean gatherTransparentRegion(Region region) {
257         // Do nothing
258         return false;
259     }
260 
261     /**
262      * Returns the surface control for the task surface. This should be parented to a screen
263      * surface for display/embedding purposes.
264      *
265      * @return the surface control for the task
266      */
getSurfaceControl()267     public SurfaceControl getSurfaceControl() {
268         return mSurfaceControl;
269     }
270 
getDisplayId()271     public int getDisplayId() {
272         return INVALID_DISPLAY;
273     }
274 
getVirtualDisplay()275     public VirtualDisplay getVirtualDisplay() {
276         return null;
277     }
278 
279     /**
280      * Set forwarded insets on the task content.
281      *
282      * @see IWindowManager#setForwardedInsets
283      */
setForwardedInsets(Insets insets)284     public void setForwardedInsets(Insets insets) {
285         // Do nothing
286     }
287 
288     /**
289      * Updates position and bounds information needed by WM and IME to manage window
290      * focus and touch events properly.
291      * <p>
292      * This should be called whenever the position or size of the surface changes
293      * or if touchable areas above the surface are added or removed.
294      */
updateLocationAndTapExcludeRegion()295     protected void updateLocationAndTapExcludeRegion() {
296         if (!isInitialized() || mHost.getWindow() == null) {
297             return;
298         }
299         applyTapExcludeRegion(mHost.getWindow(), mHost.getTapExcludeRegion());
300     }
301 
302     /**
303      * Call to update the tap exclude region for the window.
304      * <p>
305      * This should not normally be called directly, but through
306      * {@link #updateLocationAndTapExcludeRegion()}. This method
307      * is provided as an optimization when managing multiple TaskSurfaces within a view.
308      *
309      * @see IWindowSession#updateTapExcludeRegion(IWindow, Region)
310      */
applyTapExcludeRegion(IWindow window, @Nullable Region tapExcludeRegion)311     private void applyTapExcludeRegion(IWindow window, @Nullable Region tapExcludeRegion) {
312         try {
313             IWindowSession session = WindowManagerGlobal.getWindowSession();
314             session.updateTapExcludeRegion(window, tapExcludeRegion);
315         } catch (RemoteException e) {
316             e.rethrowAsRuntimeException();
317         }
318     }
319 
320     /**
321      * Removes the tap exclude region set by {@link #updateLocationAndTapExcludeRegion()}.
322      */
clearTapExcludeRegion()323     private void clearTapExcludeRegion() {
324         if (!isInitialized() || mHost.getWindow() == null) {
325             return;
326         }
327         applyTapExcludeRegion(mHost.getWindow(), null);
328     }
329 
330     /**
331      * Set the callback to be notified about state changes.
332      * <p>This class must finish initializing before {@link #startActivity(Intent)} can be called.
333      * <p>Note: If the instance was ready prior to this call being made, then
334      * {@link Listener#onInitialized()} will be called from within this method call.
335      *
336      * @param listener The listener to report events to.
337      *
338      * @see ActivityView.StateCallback
339      * @see #startActivity(Intent)
340      */
setListener(TaskEmbedder.Listener listener)341     public void setListener(TaskEmbedder.Listener listener) {
342         mListener = listener;
343         if (mListener != null && isInitialized()) {
344             mListener.onInitialized();
345         }
346     }
347 
348     /**
349      * Launch a new activity into this container.
350      *
351      * @param intent Intent used to launch an activity
352      *
353      * @see #startActivity(PendingIntent)
354      */
startActivity(@onNull Intent intent)355     public void startActivity(@NonNull Intent intent) {
356         final ActivityOptions options = prepareActivityOptions(null);
357         mContext.startActivity(intent, options.toBundle());
358     }
359 
360     /**
361      * Launch a new activity into this container.
362      *
363      * @param intent Intent used to launch an activity
364      * @param user The UserHandle of the user to start this activity for
365      *
366      * @see #startActivity(PendingIntent)
367      */
startActivity(@onNull Intent intent, UserHandle user)368     public void startActivity(@NonNull Intent intent, UserHandle user) {
369         final ActivityOptions options = prepareActivityOptions(null);
370         mContext.startActivityAsUser(intent, options.toBundle(), user);
371     }
372 
373     /**
374      * Launch a new activity into this container.
375      *
376      * @param pendingIntent Intent used to launch an activity
377      *
378      * @see #startActivity(Intent)
379      */
startActivity(@onNull PendingIntent pendingIntent)380     public void startActivity(@NonNull PendingIntent pendingIntent) {
381         final ActivityOptions options = prepareActivityOptions(null);
382         try {
383             pendingIntent.send(null /* context */, 0 /* code */, null /* intent */,
384                     null /* onFinished */, null /* handler */, null /* requiredPermission */,
385                     options.toBundle());
386         } catch (PendingIntent.CanceledException e) {
387             throw new RuntimeException(e);
388         }
389     }
390 
391     /**
392      * Launch a new activity into this container.
393      *
394      * @param pendingIntent Intent used to launch an activity
395      * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
396      * @param options options for the activity
397      *
398      * @see #startActivity(Intent)
399      */
startActivity(@onNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, @NonNull ActivityOptions options)400     public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
401             @NonNull ActivityOptions options) {
402         prepareActivityOptions(options);
403         try {
404             pendingIntent.send(mContext, 0 /* code */, fillInIntent,
405                     null /* onFinished */, null /* handler */, null /* requiredPermission */,
406                     options.toBundle());
407         } catch (PendingIntent.CanceledException e) {
408             throw new RuntimeException(e);
409         }
410     }
411 
412     /**
413      * Launch an activity represented by {@link ShortcutInfo} into this container.
414      * <p>The owner of this container must be allowed to access the shortcut information,
415      * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method.
416      *
417      * @param shortcut the shortcut used to launch the activity.
418      * @param options options for the activity.
419      * @param sourceBounds the rect containing the source bounds of the clicked icon to open
420      *                     this shortcut.
421      *
422      * @see #startActivity(Intent)
423      */
startShortcutActivity(@onNull ShortcutInfo shortcut, @NonNull ActivityOptions options, @Nullable Rect sourceBounds)424     public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
425             @NonNull ActivityOptions options, @Nullable Rect sourceBounds) {
426         LauncherApps service =
427                 (LauncherApps) mContext.getSystemService(Context.LAUNCHER_APPS_SERVICE);
428         prepareActivityOptions(options);
429         service.startShortcut(shortcut, sourceBounds, options.toBundle());
430     }
431 
432     /**
433      * Check if container is ready to launch and modify {@param options} to target the virtual
434      * display, creating them if necessary.
435      */
436     @CallSuper
prepareActivityOptions(ActivityOptions options)437     protected ActivityOptions prepareActivityOptions(ActivityOptions options) {
438         if (!isInitialized()) {
439             throw new IllegalStateException(
440                     "Trying to start activity before ActivityView is ready.");
441         }
442         if (options == null) {
443             options = ActivityOptions.makeBasic();
444         }
445         return options;
446     }
447 
448     /**
449      * Releases the resources for this TaskEmbedder. Tasks will no longer be launchable
450      * within this container.
451      *
452      * <p>Note: Calling this method is allowed after {@link Listener#onInitialized()} callback is
453      * triggered and before {@link Listener#onReleased()}.
454      */
release()455     public void release() {
456         if (!isInitialized()) {
457             throw new IllegalStateException("Trying to release container that is not initialized.");
458         }
459         performRelease();
460     }
461 
performRelease()462     private boolean performRelease() {
463         if (!mOpened) {
464             return false;
465         }
466 
467         mTransaction.reparent(mSurfaceControl, null).apply();
468         mSurfaceControl.release();
469 
470         boolean reportReleased = onRelease();
471         if (mListener != null && reportReleased) {
472             mListener.onReleased();
473         }
474         mOpened = false;
475         mGuard.close();
476         return true;
477     }
478 
479     @Override
finalize()480     protected void finalize() throws Throwable {
481         try {
482             if (mGuard != null) {
483                 mGuard.warnIfOpen();
484                 performRelease();
485             }
486         } finally {
487             super.finalize();
488         }
489     }
490 }
491