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