1 /**
2  * Copyright (c) 2017 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.app;
18 
19 import android.annotation.NonNull;
20 import android.app.ActivityManager.StackInfo;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.hardware.display.DisplayManager;
24 import android.hardware.display.VirtualDisplay;
25 import android.hardware.input.InputManager;
26 import android.os.RemoteException;
27 import android.os.UserHandle;
28 import android.util.AttributeSet;
29 import android.util.DisplayMetrics;
30 import android.util.Log;
31 import android.view.IWindowManager;
32 import android.view.InputDevice;
33 import android.view.InputEvent;
34 import android.view.MotionEvent;
35 import android.view.Surface;
36 import android.view.SurfaceHolder;
37 import android.view.SurfaceView;
38 import android.view.ViewGroup;
39 import android.view.WindowManager;
40 import android.view.WindowManagerGlobal;
41 
42 import dalvik.system.CloseGuard;
43 
44 import java.util.List;
45 
46 /**
47  * Activity container that allows launching activities into itself and does input forwarding.
48  * <p>Creation of this view is only allowed to callers who have
49  * {@link android.Manifest.permission#INJECT_EVENTS} permission.
50  * <p>Activity launching into this container is restricted by the same rules that apply to launching
51  * on VirtualDisplays.
52  * @hide
53  */
54 public class ActivityView extends ViewGroup {
55 
56     private static final String DISPLAY_NAME = "ActivityViewVirtualDisplay";
57     private static final String TAG = "ActivityView";
58 
59     private VirtualDisplay mVirtualDisplay;
60     private final SurfaceView mSurfaceView;
61     private Surface mSurface;
62 
63     private final SurfaceCallback mSurfaceCallback;
64     private StateCallback mActivityViewCallback;
65 
66     private IActivityManager mActivityManager;
67     private IInputForwarder mInputForwarder;
68     // Temp container to store view coordinates on screen.
69     private final int[] mLocationOnScreen = new int[2];
70 
71     private TaskStackListener mTaskStackListener;
72 
73     private final CloseGuard mGuard = CloseGuard.get();
74     private boolean mOpened; // Protected by mGuard.
75 
ActivityView(Context context)76     public ActivityView(Context context) {
77         this(context, null /* attrs */);
78     }
79 
ActivityView(Context context, AttributeSet attrs)80     public ActivityView(Context context, AttributeSet attrs) {
81         this(context, attrs, 0 /* defStyle */);
82     }
83 
ActivityView(Context context, AttributeSet attrs, int defStyle)84     public ActivityView(Context context, AttributeSet attrs, int defStyle) {
85         super(context, attrs, defStyle);
86 
87         mActivityManager = ActivityManager.getService();
88         mSurfaceView = new SurfaceView(context);
89         mSurfaceCallback = new SurfaceCallback();
90         mSurfaceView.getHolder().addCallback(mSurfaceCallback);
91         addView(mSurfaceView);
92 
93         mOpened = true;
94         mGuard.open("release");
95     }
96 
97     /** Callback that notifies when the container is ready or destroyed. */
98     public abstract static class StateCallback {
99         /**
100          * Called when the container is ready for launching activities. Calling
101          * {@link #startActivity(Intent)} prior to this callback will result in an
102          * {@link IllegalStateException}.
103          *
104          * @see #startActivity(Intent)
105          */
onActivityViewReady(ActivityView view)106         public abstract void onActivityViewReady(ActivityView view);
107         /**
108          * Called when the container can no longer launch activities. Calling
109          * {@link #startActivity(Intent)} after this callback will result in an
110          * {@link IllegalStateException}.
111          *
112          * @see #startActivity(Intent)
113          */
onActivityViewDestroyed(ActivityView view)114         public abstract void onActivityViewDestroyed(ActivityView view);
115         /**
116          * Called when a task is moved to the front of the stack inside the container.
117          * This is a filtered version of {@link TaskStackListener}
118          */
onTaskMovedToFront(ActivityManager.StackInfo stackInfo)119         public void onTaskMovedToFront(ActivityManager.StackInfo stackInfo) { }
120     }
121 
122     /**
123      * Set the callback to be notified about state changes.
124      * <p>This class must finish initializing before {@link #startActivity(Intent)} can be called.
125      * <p>Note: If the instance was ready prior to this call being made, then
126      * {@link StateCallback#onActivityViewReady(ActivityView)} will be called from within
127      * this method call.
128      *
129      * @param callback The callback to report events to.
130      *
131      * @see StateCallback
132      * @see #startActivity(Intent)
133      */
setCallback(StateCallback callback)134     public void setCallback(StateCallback callback) {
135         mActivityViewCallback = callback;
136 
137         if (mVirtualDisplay != null && mActivityViewCallback != null) {
138             mActivityViewCallback.onActivityViewReady(this);
139         }
140     }
141 
142     /**
143      * Launch a new activity into this container.
144      * <p>Activity resolved by the provided {@link Intent} must have
145      * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
146      * launched here. Also, if activity is not owned by the owner of this container, it must allow
147      * embedding and the caller must have permission to embed.
148      * <p>Note: This class must finish initializing and
149      * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
150      * this method can be called.
151      *
152      * @param intent Intent used to launch an activity.
153      *
154      * @see StateCallback
155      * @see #startActivity(PendingIntent)
156      */
startActivity(@onNull Intent intent)157     public void startActivity(@NonNull Intent intent) {
158         final ActivityOptions options = prepareActivityOptions();
159         getContext().startActivity(intent, options.toBundle());
160     }
161 
162     /**
163      * Launch a new activity into this container.
164      * <p>Activity resolved by the provided {@link Intent} must have
165      * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
166      * launched here. Also, if activity is not owned by the owner of this container, it must allow
167      * embedding and the caller must have permission to embed.
168      * <p>Note: This class must finish initializing and
169      * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
170      * this method can be called.
171      *
172      * @param intent Intent used to launch an activity.
173      * @param user The UserHandle of the user to start this activity for.
174      *
175      *
176      * @see StateCallback
177      * @see #startActivity(PendingIntent)
178      */
startActivity(@onNull Intent intent, UserHandle user)179     public void startActivity(@NonNull Intent intent, UserHandle user) {
180         final ActivityOptions options = prepareActivityOptions();
181         getContext().startActivityAsUser(intent, options.toBundle(), user);
182     }
183 
184     /**
185      * Launch a new activity into this container.
186      * <p>Activity resolved by the provided {@link PendingIntent} must have
187      * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
188      * launched here. Also, if activity is not owned by the owner of this container, it must allow
189      * embedding and the caller must have permission to embed.
190      * <p>Note: This class must finish initializing and
191      * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
192      * this method can be called.
193      *
194      * @param pendingIntent Intent used to launch an activity.
195      *
196      * @see StateCallback
197      * @see #startActivity(Intent)
198      */
startActivity(@onNull PendingIntent pendingIntent)199     public void startActivity(@NonNull PendingIntent pendingIntent) {
200         final ActivityOptions options = prepareActivityOptions();
201         try {
202             pendingIntent.send(null /* context */, 0 /* code */, null /* intent */,
203                     null /* onFinished */, null /* handler */, null /* requiredPermission */,
204                     options.toBundle());
205         } catch (PendingIntent.CanceledException e) {
206             throw new RuntimeException(e);
207         }
208     }
209 
210     /**
211      * Check if container is ready to launch and create {@link ActivityOptions} to target the
212      * virtual display.
213      */
prepareActivityOptions()214     private ActivityOptions prepareActivityOptions() {
215         if (mVirtualDisplay == null) {
216             throw new IllegalStateException(
217                     "Trying to start activity before ActivityView is ready.");
218         }
219         final ActivityOptions options = ActivityOptions.makeBasic();
220         options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
221         return options;
222     }
223 
224     /**
225      * Release this container. Activity launching will no longer be permitted.
226      * <p>Note: Calling this method is allowed after
227      * {@link StateCallback#onActivityViewReady(ActivityView)} callback was triggered and before
228      * {@link StateCallback#onActivityViewDestroyed(ActivityView)}.
229      *
230      * @see StateCallback
231      */
release()232     public void release() {
233         if (mVirtualDisplay == null) {
234             throw new IllegalStateException(
235                     "Trying to release container that is not initialized.");
236         }
237         performRelease();
238     }
239 
240     /**
241      * Triggers an update of {@link ActivityView}'s location on screen to properly set touch exclude
242      * regions and avoid focus switches by touches on this view.
243      */
onLocationChanged()244     public void onLocationChanged() {
245         updateLocation();
246     }
247 
248     @Override
onLayout(boolean changed, int l, int t, int r, int b)249     public void onLayout(boolean changed, int l, int t, int r, int b) {
250         mSurfaceView.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */);
251     }
252 
253     /** Send current location and size to the WM to set tap exclude region for this view. */
updateLocation()254     private void updateLocation() {
255         try {
256             getLocationOnScreen(mLocationOnScreen);
257             WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
258                     mLocationOnScreen[0], mLocationOnScreen[1], getWidth(), getHeight());
259         } catch (RemoteException e) {
260             e.rethrowAsRuntimeException();
261         }
262     }
263 
264     @Override
onTouchEvent(MotionEvent event)265     public boolean onTouchEvent(MotionEvent event) {
266         return injectInputEvent(event) || super.onTouchEvent(event);
267     }
268 
269     @Override
onGenericMotionEvent(MotionEvent event)270     public boolean onGenericMotionEvent(MotionEvent event) {
271         if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
272             if (injectInputEvent(event)) {
273                 return true;
274             }
275         }
276         return super.onGenericMotionEvent(event);
277     }
278 
injectInputEvent(InputEvent event)279     private boolean injectInputEvent(InputEvent event) {
280         if (mInputForwarder != null) {
281             try {
282                 return mInputForwarder.forwardEvent(event);
283             } catch (RemoteException e) {
284                 e.rethrowAsRuntimeException();
285             }
286         }
287         return false;
288     }
289 
290     private class SurfaceCallback implements SurfaceHolder.Callback {
291         @Override
surfaceCreated(SurfaceHolder surfaceHolder)292         public void surfaceCreated(SurfaceHolder surfaceHolder) {
293             mSurface = mSurfaceView.getHolder().getSurface();
294             if (mVirtualDisplay == null) {
295                 initVirtualDisplay();
296                 if (mVirtualDisplay != null && mActivityViewCallback != null) {
297                     mActivityViewCallback.onActivityViewReady(ActivityView.this);
298                 }
299             } else {
300                 mVirtualDisplay.setSurface(surfaceHolder.getSurface());
301             }
302             updateLocation();
303         }
304 
305         @Override
surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height)306         public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
307             if (mVirtualDisplay != null) {
308                 mVirtualDisplay.resize(width, height, getBaseDisplayDensity());
309             }
310             updateLocation();
311         }
312 
313         @Override
surfaceDestroyed(SurfaceHolder surfaceHolder)314         public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
315             mSurface.release();
316             mSurface = null;
317             if (mVirtualDisplay != null) {
318                 mVirtualDisplay.setSurface(null);
319             }
320             cleanTapExcludeRegion();
321         }
322     }
323 
initVirtualDisplay()324     private void initVirtualDisplay() {
325         if (mVirtualDisplay != null) {
326             throw new IllegalStateException("Trying to initialize for the second time.");
327         }
328 
329         final int width = mSurfaceView.getWidth();
330         final int height = mSurfaceView.getHeight();
331         final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
332         mVirtualDisplay = displayManager.createVirtualDisplay(
333                 DISPLAY_NAME + "@" + System.identityHashCode(this),
334                 width, height, getBaseDisplayDensity(), mSurface,
335                 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
336                         | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
337         if (mVirtualDisplay == null) {
338             Log.e(TAG, "Failed to initialize ActivityView");
339             return;
340         }
341 
342         final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
343         final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
344         try {
345             wm.dontOverrideDisplayInfo(displayId);
346         } catch (RemoteException e) {
347             e.rethrowAsRuntimeException();
348         }
349         mInputForwarder = InputManager.getInstance().createInputForwarder(displayId);
350         mTaskStackListener = new TaskStackListenerImpl();
351         try {
352             mActivityManager.registerTaskStackListener(mTaskStackListener);
353         } catch (RemoteException e) {
354             Log.e(TAG, "Failed to register task stack listener", e);
355         }
356     }
357 
performRelease()358     private void performRelease() {
359         if (!mOpened) {
360             return;
361         }
362 
363         mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
364 
365         if (mInputForwarder != null) {
366             mInputForwarder = null;
367         }
368         cleanTapExcludeRegion();
369 
370         if (mTaskStackListener != null) {
371             try {
372                 mActivityManager.unregisterTaskStackListener(mTaskStackListener);
373             } catch (RemoteException e) {
374                 Log.e(TAG, "Failed to unregister task stack listener", e);
375             }
376             mTaskStackListener = null;
377         }
378 
379         final boolean displayReleased;
380         if (mVirtualDisplay != null) {
381             mVirtualDisplay.release();
382             mVirtualDisplay = null;
383             displayReleased = true;
384         } else {
385             displayReleased = false;
386         }
387 
388         if (mSurface != null) {
389             mSurface.release();
390             mSurface = null;
391         }
392 
393         if (displayReleased && mActivityViewCallback != null) {
394             mActivityViewCallback.onActivityViewDestroyed(this);
395         }
396 
397         mGuard.close();
398         mOpened = false;
399     }
400 
401     /** Report to server that tap exclude region on hosting display should be cleared. */
cleanTapExcludeRegion()402     private void cleanTapExcludeRegion() {
403         // Update tap exclude region with an empty rect to clean the state on server.
404         try {
405             WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
406                     0 /* left */, 0 /* top */, 0 /* width */, 0 /* height */);
407         } catch (RemoteException e) {
408             e.rethrowAsRuntimeException();
409         }
410     }
411 
412     /** Get density of the hosting display. */
getBaseDisplayDensity()413     private int getBaseDisplayDensity() {
414         final WindowManager wm = mContext.getSystemService(WindowManager.class);
415         final DisplayMetrics metrics = new DisplayMetrics();
416         wm.getDefaultDisplay().getMetrics(metrics);
417         return metrics.densityDpi;
418     }
419 
420     @Override
finalize()421     protected void finalize() throws Throwable {
422         try {
423             if (mGuard != null) {
424                 mGuard.warnIfOpen();
425                 performRelease();
426             }
427         } finally {
428             super.finalize();
429         }
430     }
431 
432     /**
433      * A task change listener that detects background color change of the topmost stack on our
434      * virtual display and updates the background of the surface view. This background will be shown
435      * when surface view is resized, but the app hasn't drawn its content in new size yet.
436      * It also calls StateCallback.onTaskMovedToFront to notify interested parties that the stack
437      * associated with the {@link ActivityView} has had a Task moved to the front. This is useful
438      * when needing to also bring the host Activity to the foreground at the same time.
439      */
440     private class TaskStackListenerImpl extends TaskStackListener {
441 
442         @Override
onTaskDescriptionChanged(int taskId, ActivityManager.TaskDescription td)443         public void onTaskDescriptionChanged(int taskId, ActivityManager.TaskDescription td)
444                 throws RemoteException {
445             if (mVirtualDisplay == null) {
446                 return;
447             }
448 
449             StackInfo stackInfo = getTopMostStackInfo();
450             if (stackInfo == null) {
451                 return;
452             }
453             // Found the topmost stack on target display. Now check if the topmost task's
454             // description changed.
455             if (taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
456                 mSurfaceView.setResizeBackgroundColor(td.getBackgroundColor());
457             }
458         }
459 
460         @Override
onTaskMovedToFront(int taskId)461         public void onTaskMovedToFront(int taskId) throws RemoteException {
462             if (mActivityViewCallback  != null) {
463                 StackInfo stackInfo = getTopMostStackInfo();
464                 // if StackInfo was null or unrelated to the "move to front" then there's no use
465                 // notifying the callback
466                 if (stackInfo != null
467                         && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
468                     mActivityViewCallback.onTaskMovedToFront(stackInfo);
469                 }
470             }
471         }
472 
getTopMostStackInfo()473         private StackInfo getTopMostStackInfo() throws RemoteException {
474             // Find the topmost task on our virtual display - it will define the background
475             // color of the surface view during resizing.
476             final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
477             final List<StackInfo> stackInfoList = mActivityManager.getAllStackInfos();
478 
479             // Iterate through stacks from top to bottom.
480             final int stackCount = stackInfoList.size();
481             for (int i = 0; i < stackCount; i++) {
482                 final StackInfo stackInfo = stackInfoList.get(i);
483                 // Only look for stacks on our virtual display.
484                 if (stackInfo.displayId != displayId) {
485                     continue;
486                 }
487                 // Found the topmost stack on target display.
488                 return stackInfo;
489             }
490             return null;
491         }
492     }
493 }
494