blob: 7ad8f22d346eaafdf42c5d99c9532109366cb987 [file] [log] [blame]
Winson Chunga1f869d2020-03-21 23:02:48 -07001/*
2 * Copyright (C) 2019 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
17package android.app;
18
19import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
20import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
21import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
22import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
23import static android.view.Display.INVALID_DISPLAY;
24
25import android.annotation.Nullable;
26import android.content.ComponentName;
27import android.content.Context;
28import android.graphics.Insets;
29import android.graphics.Matrix;
30import android.graphics.Point;
31import android.graphics.Region;
32import android.hardware.display.DisplayManager;
33import android.hardware.display.VirtualDisplay;
34import android.hardware.input.InputManager;
35import android.os.RemoteException;
36import android.os.SystemClock;
37import android.util.DisplayMetrics;
38import android.util.Log;
39import android.view.IWindow;
40import android.view.IWindowManager;
41import android.view.IWindowSession;
42import android.view.InputDevice;
43import android.view.KeyCharacterMap;
44import android.view.KeyEvent;
45import android.view.WindowManagerGlobal;
46import android.view.inputmethod.InputMethodManager;
47
48import java.util.List;
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 */
56public class VirtualDisplayTaskEmbedder extends TaskEmbedder {
57 private static final String TAG = "VirDispTaskEmbedder";
58 private static final String DISPLAY_NAME = "TaskVirtualDisplay";
59
60 // For Virtual Displays
61 private int mDisplayDensityDpi;
62 private final boolean mSingleTaskInstance;
63 private VirtualDisplay mVirtualDisplay;
64 private Insets mForwardedInsets;
65 private DisplayMetrics mTmpDisplayMetrics;
66
67 /**
68 * Constructs a new TaskEmbedder.
69 *
70 * @param context the context
71 * @param host the host for this embedded task
72 * @param singleTaskInstance whether to apply a single-task constraint to this container,
73 * only applicable if virtual displays are used
74 */
75 VirtualDisplayTaskEmbedder(Context context, VirtualDisplayTaskEmbedder.Host host,
76 boolean singleTaskInstance) {
77 super(context, host);
78 mSingleTaskInstance = singleTaskInstance;
79 }
80
81 @Override
82 public TaskStackListener createTaskStackListener() {
83 return new TaskStackListenerImpl();
84 }
85
86 /**
87 * Whether this container has been initialized.
88 *
89 * @return true if initialized
90 */
91 @Override
92 public boolean isInitialized() {
93 return mVirtualDisplay != null;
94 }
95
96 @Override
97 public boolean onInitialize() {
98 final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
99 mDisplayDensityDpi = getBaseDisplayDensity();
100 mVirtualDisplay = displayManager.createVirtualDisplay(
101 DISPLAY_NAME + "@" + System.identityHashCode(this), mHost.getWidth(),
102 mHost.getHeight(), mDisplayDensityDpi, null,
103 VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
104 | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
105
106 if (mVirtualDisplay == null) {
107 Log.e(TAG, "Failed to initialize TaskEmbedder");
108 return false;
109 }
110
111 try {
112 // TODO: Find a way to consolidate these calls to the server.
113 final int displayId = getDisplayId();
114 final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
115 WindowManagerGlobal.getWindowSession().reparentDisplayContent(
116 mHost.getWindow(), mSurfaceControl, displayId);
117 wm.dontOverrideDisplayInfo(displayId);
118 if (mSingleTaskInstance) {
119 mContext.getSystemService(ActivityTaskManager.class)
120 .setDisplayToSingleTaskInstance(displayId);
121 }
122 setForwardedInsets(mForwardedInsets);
123 } catch (RemoteException e) {
124 e.rethrowAsRuntimeException();
125 }
126
127 if (mHost.getWindow() != null) {
128 updateLocationAndTapExcludeRegion();
129 }
130 return true;
131 }
132
133 @Override
134 protected boolean onRelease() {
135 // Clear activity view geometry for IME on this display
136 clearActivityViewGeometryForIme();
137
138 // Clear tap-exclude region (if any) for this window.
139 clearTapExcludeRegion();
140
141 if (isInitialized()) {
142 mVirtualDisplay.release();
143 mVirtualDisplay = null;
144 return true;
145 }
146 return false;
147 }
148
149 /**
150 * Starts presentation of tasks in this container.
151 */
152 @Override
153 public void start() {
154 if (isInitialized()) {
155 mVirtualDisplay.setDisplayState(true);
156 updateLocationAndTapExcludeRegion();
157 }
158 }
159
160 /**
161 * Stops presentation of tasks in this container.
162 */
163 @Override
164 public void stop() {
165 if (isInitialized()) {
166 mVirtualDisplay.setDisplayState(false);
167 clearActivityViewGeometryForIme();
168 clearTapExcludeRegion();
169 }
170 }
171
172 /**
173 * This should be called whenever the position or size of the surface changes
174 * or if touchable areas above the surface are added or removed.
175 */
176 @Override
177 public void notifyBoundsChanged() {
178 updateLocationAndTapExcludeRegion();
179 }
180
181 /**
182 * Called to update the dimensions whenever the host size changes.
183 *
184 * @param width the new width of the surface
185 * @param height the new height of the surface
186 */
187 @Override
188 public void resizeTask(int width, int height) {
189 mDisplayDensityDpi = getBaseDisplayDensity();
190 if (isInitialized()) {
191 mVirtualDisplay.resize(width, height, mDisplayDensityDpi);
192 }
193 }
194
195 /**
196 * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the
197 * virtual display.
198 */
199 @Override
200 public void performBackPress() {
201 if (!isInitialized()) {
202 return;
203 }
204 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
205 final InputManager im = InputManager.getInstance();
206 im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, displayId),
207 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
208 im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, displayId),
209 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
210 }
211
212 @Override
213 public boolean gatherTransparentRegion(Region region) {
214 // The tap exclude region may be affected by any view on top of it, so we detect the
215 // possible change by monitoring this function. The tap exclude region is only used
216 // for virtual displays.
217 notifyBoundsChanged();
218 return super.gatherTransparentRegion(region);
219 }
220
221 /** An opaque unique identifier for this task surface among others being managed by the app. */
222 @Override
223 public int getId() {
224 return getDisplayId();
225 }
226
227 @Override
228 public int getDisplayId() {
229 if (isInitialized()) {
230 return mVirtualDisplay.getDisplay().getDisplayId();
231 }
232 return INVALID_DISPLAY;
233 }
234
235 /**
236 * Check if container is ready to launch and create {@link ActivityOptions} to target the
237 * virtual display.
238 * @param options The existing options to amend, or null if the caller wants new options to be
239 * created
240 */
241 @Override
242 protected ActivityOptions prepareActivityOptions(ActivityOptions options) {
243 options = super.prepareActivityOptions(options);
244 options.setLaunchDisplayId(getDisplayId());
245 options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
246 options.setTaskAlwaysOnTop(true);
247 return options;
248 }
249
250 /**
251 * Set forwarded insets on the virtual display.
252 *
253 * @see IWindowManager#setForwardedInsets
254 */
255 @Override
256 public void setForwardedInsets(Insets insets) {
257 mForwardedInsets = insets;
258 if (!isInitialized()) {
259 return;
260 }
261 try {
262 final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
263 wm.setForwardedInsets(getDisplayId(), mForwardedInsets);
264 } catch (RemoteException e) {
265 e.rethrowAsRuntimeException();
266 }
267 }
268
269 /**
270 * Updates position and bounds information needed by WM and IME to manage window
271 * focus and touch events properly.
272 * <p>
273 * This should be called whenever the position or size of the surface changes
274 * or if touchable areas above the surface are added or removed.
275 */
276 private void updateLocationAndTapExcludeRegion() {
277 if (!isInitialized() || mHost.getWindow() == null) {
278 return;
279 }
280 reportLocation(mHost.getScreenToTaskMatrix(), mHost.getPositionInWindow());
281 applyTapExcludeRegion(mHost.getWindow(), mHost.getTapExcludeRegion());
282 }
283
284 /**
285 * Call to update the position and transform matrix for the embedded surface.
286 * <p>
287 * This should not normally be called directly, but through
288 * {@link #updateLocationAndTapExcludeRegion()}. This method
289 * is provided as an optimization when managing multiple TaskSurfaces within a view.
290 *
291 * @param screenToViewMatrix the matrix/transform from screen space to view space
292 * @param positionInWindow the window-relative position of the surface
293 *
294 * @see InputMethodManager#reportActivityView(int, Matrix)
295 */
296 private void reportLocation(Matrix screenToViewMatrix, Point positionInWindow) {
297 try {
298 final int displayId = getDisplayId();
299 mContext.getSystemService(InputMethodManager.class)
300 .reportActivityView(displayId, screenToViewMatrix);
301 IWindowSession session = WindowManagerGlobal.getWindowSession();
302 session.updateDisplayContentLocation(mHost.getWindow(), positionInWindow.x,
303 positionInWindow.y, displayId);
304 } catch (RemoteException e) {
305 e.rethrowAsRuntimeException();
306 }
307 }
308
309 /**
310 * Call to update the tap exclude region for the window.
311 * <p>
312 * This should not normally be called directly, but through
313 * {@link #updateLocationAndTapExcludeRegion()}. This method
314 * is provided as an optimization when managing multiple TaskSurfaces within a view.
315 *
316 * @see IWindowSession#updateTapExcludeRegion(IWindow, Region)
317 */
318 private void applyTapExcludeRegion(IWindow window, @Nullable Region tapExcludeRegion) {
319 try {
320 IWindowSession session = WindowManagerGlobal.getWindowSession();
321 session.updateTapExcludeRegion(window, tapExcludeRegion);
322 } catch (RemoteException e) {
323 e.rethrowAsRuntimeException();
324 }
325 }
326
327 /**
328 * @see InputMethodManager#reportActivityView(int, Matrix)
329 */
330 private void clearActivityViewGeometryForIme() {
331 final int displayId = getDisplayId();
332 mContext.getSystemService(InputMethodManager.class).reportActivityView(displayId, null);
333 }
334
335 /**
336 * Removes the tap exclude region set by {@link #updateLocationAndTapExcludeRegion()}.
337 */
338 private void clearTapExcludeRegion() {
339 if (mHost.getWindow() == null) {
340 Log.w(TAG, "clearTapExcludeRegion: not attached to window!");
341 return;
342 }
343 applyTapExcludeRegion(mHost.getWindow(), null);
344 }
345
346 private static KeyEvent createKeyEvent(int action, int code, int displayId) {
347 long when = SystemClock.uptimeMillis();
348 final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
349 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
350 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
351 InputDevice.SOURCE_KEYBOARD);
352 ev.setDisplayId(displayId);
353 return ev;
354 }
355
356 /** Get density of the hosting display. */
357 private int getBaseDisplayDensity() {
358 if (mTmpDisplayMetrics == null) {
359 mTmpDisplayMetrics = new DisplayMetrics();
360 }
361 mContext.getDisplayNoVerify().getRealMetrics(mTmpDisplayMetrics);
362 return mTmpDisplayMetrics.densityDpi;
363 }
364
365 /**
366 * A task change listener that detects background color change of the topmost stack on our
367 * virtual display and updates the background of the surface view. This background will be shown
368 * when surface view is resized, but the app hasn't drawn its content in new size yet.
369 * It also calls StateCallback.onTaskMovedToFront to notify interested parties that the stack
370 * associated with the {@link ActivityView} has had a Task moved to the front. This is useful
371 * when needing to also bring the host Activity to the foreground at the same time.
372 */
373 private class TaskStackListenerImpl extends TaskStackListener {
374
375 @Override
376 public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo)
377 throws RemoteException {
378 if (!isInitialized()) {
379 return;
380 }
381 if (taskInfo.displayId != getDisplayId()) {
382 return;
383 }
384 ActivityManager.StackInfo stackInfo = getTopMostStackInfo();
385 if (stackInfo == null) {
386 return;
387 }
388 // Found the topmost stack on target display. Now check if the topmost task's
389 // description changed.
390 if (taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
391 mHost.onTaskBackgroundColorChanged(VirtualDisplayTaskEmbedder.this,
392 taskInfo.taskDescription.getBackgroundColor());
393 }
394 }
395
396 @Override
397 public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)
398 throws RemoteException {
399 if (!isInitialized() || mListener == null
400 || taskInfo.displayId != getDisplayId()) {
401 return;
402 }
403
404 ActivityManager.StackInfo stackInfo = getTopMostStackInfo();
405 // if StackInfo was null or unrelated to the "move to front" then there's no use
406 // notifying the callback
407 if (stackInfo != null
408 && taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
409 mListener.onTaskMovedToFront(taskInfo.taskId);
410 }
411 }
412
413 @Override
414 public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
415 if (mListener == null || !isInitialized()) {
416 return;
417 }
418
419 ActivityManager.StackInfo stackInfo = getTopMostStackInfo();
420 // if StackInfo was null or unrelated to the task creation then there's no use
421 // notifying the callback
422 if (stackInfo != null
423 && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
424 mListener.onTaskCreated(taskId, componentName);
425 }
426 }
427
428 @Override
429 public void onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo)
430 throws RemoteException {
431 if (mListener == null || !isInitialized()
432 || taskInfo.displayId != getDisplayId()) {
433 return;
434 }
435
436 mListener.onTaskRemovalStarted(taskInfo.taskId);
437 }
438
439 private ActivityManager.StackInfo getTopMostStackInfo() throws RemoteException {
440 // Find the topmost task on our virtual display - it will define the background
441 // color of the surface view during resizing.
442 final int displayId = getDisplayId();
443 final List<ActivityManager.StackInfo> stackInfoList =
444 mActivityTaskManager.getAllStackInfosOnDisplay(displayId);
445 if (stackInfoList.isEmpty()) {
446 return null;
447 }
448 return stackInfoList.get(0);
449 }
450 }
451}