Home | History | Annotate | Download | only in app
      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 
     76     public ActivityView(Context context) {
     77         this(context, null /* attrs */);
     78     }
     79 
     80     public ActivityView(Context context, AttributeSet attrs) {
     81         this(context, attrs, 0 /* defStyle */);
     82     }
     83 
     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          */
    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          */
    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          */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    244     public void onLocationChanged() {
    245         updateLocation();
    246     }
    247 
    248     @Override
    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. */
    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
    265     public boolean onTouchEvent(MotionEvent event) {
    266         return injectInputEvent(event) || super.onTouchEvent(event);
    267     }
    268 
    269     @Override
    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 
    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
    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
    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
    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 
    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 
    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. */
    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. */
    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
    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
    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
    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 
    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