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