1 /* 2 * Copyright (C) 2020 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 com.android.systemui.shade; 18 19 import static android.os.Trace.TRACE_TAG_APP; 20 21 import static com.android.systemui.Flags.enableViewCaptureTracing; 22 import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG; 23 24 import android.annotation.ColorInt; 25 import android.annotation.DrawableRes; 26 import android.annotation.LayoutRes; 27 import android.content.Context; 28 import android.content.res.Configuration; 29 import android.graphics.Canvas; 30 import android.graphics.Paint; 31 import android.graphics.Rect; 32 import android.graphics.drawable.Drawable; 33 import android.media.permission.SafeCloseable; 34 import android.net.Uri; 35 import android.os.Bundle; 36 import android.os.Trace; 37 import android.util.AttributeSet; 38 import android.view.ActionMode; 39 import android.view.InputQueue; 40 import android.view.KeyEvent; 41 import android.view.LayoutInflater; 42 import android.view.Menu; 43 import android.view.MenuItem; 44 import android.view.MotionEvent; 45 import android.view.SurfaceHolder; 46 import android.view.View; 47 import android.view.ViewGroup; 48 import android.view.ViewTreeObserver; 49 import android.view.Window; 50 import android.view.WindowInsetsController; 51 52 import com.android.app.viewcapture.ViewCaptureFactory; 53 import com.android.internal.view.FloatingActionMode; 54 import com.android.internal.widget.floatingtoolbar.FloatingToolbar; 55 import com.android.systemui.scene.ui.view.WindowRootView; 56 57 /** 58 * Combined keyguard and notification panel view. Also holding backdrop and scrims. This view can 59 * serve as the root view of the main SysUI window, but because other views can also serve that 60 * purpose, users of this class cannot assume it is the root. 61 */ 62 public class NotificationShadeWindowView extends WindowRootView { 63 public static final String TAG = "NotificationShadeWindowView"; 64 65 // Implements the floating action mode for TextView's Cut/Copy/Past menu. Normally provided by 66 // DecorView, but since this is a special window we have to roll our own. 67 private View mFloatingActionModeOriginatingView; 68 private ActionMode mFloatingActionMode; 69 private FloatingToolbar mFloatingToolbar; 70 private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener; 71 72 private InteractionEventHandler mInteractionEventHandler; 73 74 private SafeCloseable mViewCaptureCloseable; 75 NotificationShadeWindowView(Context context, AttributeSet attrs)76 public NotificationShadeWindowView(Context context, AttributeSet attrs) { 77 super(context, attrs); 78 setMotionEventSplittingEnabled(false); 79 } 80 81 @Override onAttachedToWindow()82 protected void onAttachedToWindow() { 83 super.onAttachedToWindow(); 84 setWillNotDraw(!DEBUG); 85 if (enableViewCaptureTracing()) { 86 mViewCaptureCloseable = ViewCaptureFactory.getInstance(getContext()) 87 .startCapture(getRootView(), ".NotificationShadeWindowView"); 88 } 89 } 90 91 @Override onDetachedFromWindow()92 protected void onDetachedFromWindow() { 93 super.onDetachedFromWindow(); 94 if (mViewCaptureCloseable != null) { 95 mViewCaptureCloseable.close(); 96 } 97 } 98 99 @Override dispatchKeyEvent(KeyEvent event)100 public boolean dispatchKeyEvent(KeyEvent event) { 101 mInteractionEventHandler.collectKeyEvent(event); 102 103 if (mInteractionEventHandler.interceptMediaKey(event)) { 104 return true; 105 } 106 107 if (super.dispatchKeyEvent(event)) { 108 return true; 109 } 110 111 return mInteractionEventHandler.dispatchKeyEvent(event); 112 } 113 114 @Override dispatchKeyEventPreIme(KeyEvent event)115 public boolean dispatchKeyEventPreIme(KeyEvent event) { 116 return mInteractionEventHandler.dispatchKeyEventPreIme(event); 117 } 118 setInteractionEventHandler(InteractionEventHandler listener)119 protected void setInteractionEventHandler(InteractionEventHandler listener) { 120 mInteractionEventHandler = listener; 121 } 122 123 @Override dispatchTouchEvent(MotionEvent ev)124 public boolean dispatchTouchEvent(MotionEvent ev) { 125 Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev); 126 127 result = result != null ? result : super.dispatchTouchEvent(ev); 128 129 TouchLogger.logDispatchTouch(TAG, ev, result); 130 131 mInteractionEventHandler.dispatchTouchEventComplete(); 132 133 return result; 134 } 135 136 @Override onInterceptTouchEvent(MotionEvent ev)137 public boolean onInterceptTouchEvent(MotionEvent ev) { 138 boolean intercept = mInteractionEventHandler.shouldInterceptTouchEvent(ev); 139 if (!intercept) { 140 intercept = super.onInterceptTouchEvent(ev); 141 } 142 if (intercept) { 143 mInteractionEventHandler.didIntercept(ev); 144 } 145 146 return intercept; 147 } 148 149 @Override onTouchEvent(MotionEvent ev)150 public boolean onTouchEvent(MotionEvent ev) { 151 boolean handled = mInteractionEventHandler.handleTouchEvent(ev); 152 153 if (!handled) { 154 handled = super.onTouchEvent(ev); 155 } 156 157 if (!handled) { 158 mInteractionEventHandler.didNotHandleTouchEvent(ev); 159 } 160 161 return handled; 162 } 163 164 @Override onDraw(Canvas canvas)165 public void onDraw(Canvas canvas) { 166 super.onDraw(canvas); 167 if (DEBUG) { 168 Paint pt = new Paint(); 169 pt.setColor(0x80FFFF00); 170 pt.setStrokeWidth(12.0f); 171 pt.setStyle(Paint.Style.STROKE); 172 canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), pt); 173 } 174 } 175 176 @Override startActionModeForChild(View originalView, ActionMode.Callback callback, int type)177 public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback, 178 int type) { 179 if (type == ActionMode.TYPE_FLOATING) { 180 return startActionMode(originalView, callback); 181 } 182 return super.startActionModeForChild(originalView, callback, type); 183 } 184 createFloatingActionMode( View originatingView, ActionMode.Callback2 callback)185 private ActionMode createFloatingActionMode( 186 View originatingView, ActionMode.Callback2 callback) { 187 if (mFloatingActionMode != null) { 188 mFloatingActionMode.finish(); 189 } 190 cleanupFloatingActionModeViews(); 191 mFloatingToolbar = new FloatingToolbar(mFakeWindow); 192 final FloatingActionMode mode = 193 new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar); 194 mFloatingActionModeOriginatingView = originatingView; 195 mFloatingToolbarPreDrawListener = () -> { 196 mode.updateViewLocationInWindow(); 197 return true; 198 }; 199 return mode; 200 } 201 setHandledFloatingActionMode(ActionMode mode)202 private void setHandledFloatingActionMode(ActionMode mode) { 203 mFloatingActionMode = mode; 204 mFloatingActionMode.invalidate(); // Will show the floating toolbar if necessary. 205 mFloatingActionModeOriginatingView.getViewTreeObserver() 206 .addOnPreDrawListener(mFloatingToolbarPreDrawListener); 207 } 208 cleanupFloatingActionModeViews()209 private void cleanupFloatingActionModeViews() { 210 if (mFloatingToolbar != null) { 211 mFloatingToolbar.dismiss(); 212 mFloatingToolbar = null; 213 } 214 if (mFloatingActionModeOriginatingView != null) { 215 if (mFloatingToolbarPreDrawListener != null) { 216 mFloatingActionModeOriginatingView.getViewTreeObserver() 217 .removeOnPreDrawListener(mFloatingToolbarPreDrawListener); 218 mFloatingToolbarPreDrawListener = null; 219 } 220 mFloatingActionModeOriginatingView = null; 221 } 222 } 223 startActionMode( View originatingView, ActionMode.Callback callback)224 private ActionMode startActionMode( 225 View originatingView, ActionMode.Callback callback) { 226 ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback); 227 ActionMode mode = createFloatingActionMode(originatingView, wrappedCallback); 228 if (wrappedCallback.onCreateActionMode(mode, mode.getMenu())) { 229 setHandledFloatingActionMode(mode); 230 } else { 231 mode = null; 232 } 233 return mode; 234 } 235 236 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)237 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 238 Trace.beginSection("NotificationShadeWindowView#onMeasure"); 239 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 240 Trace.endSection(); 241 } 242 243 @Override requestLayout()244 public void requestLayout() { 245 Trace.instant(TRACE_TAG_APP, "NotificationShadeWindowView#requestLayout"); 246 super.requestLayout(); 247 } 248 249 private class ActionModeCallback2Wrapper extends ActionMode.Callback2 { 250 private final ActionMode.Callback mWrapped; 251 ActionModeCallback2Wrapper(ActionMode.Callback wrapped)252 ActionModeCallback2Wrapper(ActionMode.Callback wrapped) { 253 mWrapped = wrapped; 254 } 255 onCreateActionMode(ActionMode mode, Menu menu)256 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 257 return mWrapped.onCreateActionMode(mode, menu); 258 } 259 onPrepareActionMode(ActionMode mode, Menu menu)260 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 261 requestFitSystemWindows(); 262 return mWrapped.onPrepareActionMode(mode, menu); 263 } 264 onActionItemClicked(ActionMode mode, MenuItem item)265 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 266 return mWrapped.onActionItemClicked(mode, item); 267 } 268 onDestroyActionMode(ActionMode mode)269 public void onDestroyActionMode(ActionMode mode) { 270 mWrapped.onDestroyActionMode(mode); 271 if (mode == mFloatingActionMode) { 272 cleanupFloatingActionModeViews(); 273 mFloatingActionMode = null; 274 } 275 requestFitSystemWindows(); 276 } 277 278 @Override onGetContentRect(ActionMode mode, View view, Rect outRect)279 public void onGetContentRect(ActionMode mode, View view, Rect outRect) { 280 if (mWrapped instanceof ActionMode.Callback2) { 281 ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect); 282 } else { 283 super.onGetContentRect(mode, view, outRect); 284 } 285 } 286 } 287 288 interface InteractionEventHandler { 289 /** 290 * Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer 291 * to the super method. 292 */ handleDispatchTouchEvent(MotionEvent ev)293 Boolean handleDispatchTouchEvent(MotionEvent ev); 294 295 /** 296 * Called after all dispatching is done. 297 */ 298 dispatchTouchEventComplete()299 void dispatchTouchEventComplete(); 300 301 /** 302 * Returns if the view should intercept the touch event. 303 * 304 * The touch event may still be interecepted if 305 * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)} decides to do so. 306 */ shouldInterceptTouchEvent(MotionEvent ev)307 boolean shouldInterceptTouchEvent(MotionEvent ev); 308 309 /** 310 * Called when the view decides to intercept the touch event. 311 */ didIntercept(MotionEvent ev)312 void didIntercept(MotionEvent ev); 313 handleTouchEvent(MotionEvent ev)314 boolean handleTouchEvent(MotionEvent ev); 315 didNotHandleTouchEvent(MotionEvent ev)316 void didNotHandleTouchEvent(MotionEvent ev); 317 interceptMediaKey(KeyEvent event)318 boolean interceptMediaKey(KeyEvent event); 319 dispatchKeyEvent(KeyEvent event)320 boolean dispatchKeyEvent(KeyEvent event); 321 dispatchKeyEventPreIme(KeyEvent event)322 boolean dispatchKeyEventPreIme(KeyEvent event); 323 324 /** 325 * Collects the KeyEvent without intercepting it 326 */ collectKeyEvent(KeyEvent event)327 void collectKeyEvent(KeyEvent event); 328 } 329 330 /** 331 * Minimal window to satisfy FloatingToolbar. 332 */ 333 private final Window mFakeWindow = new Window(mContext) { 334 @Override 335 public void takeSurface(SurfaceHolder.Callback2 callback) { 336 } 337 338 @Override 339 public void takeInputQueue(InputQueue.Callback callback) { 340 } 341 342 @Override 343 public boolean isFloating() { 344 return false; 345 } 346 347 @Override 348 public void alwaysReadCloseOnTouchAttr() { 349 } 350 351 @Override 352 public void setContentView(@LayoutRes int layoutResID) { 353 } 354 355 @Override 356 public void setContentView(View view) { 357 } 358 359 @Override 360 public void setContentView(View view, ViewGroup.LayoutParams params) { 361 } 362 363 @Override 364 public void addContentView(View view, ViewGroup.LayoutParams params) { 365 } 366 367 @Override 368 public void clearContentView() { 369 } 370 371 @Override 372 public View getCurrentFocus() { 373 return null; 374 } 375 376 @Override 377 public LayoutInflater getLayoutInflater() { 378 return null; 379 } 380 381 @Override 382 public void setTitle(CharSequence title) { 383 } 384 385 @Override 386 public void setTitleColor(@ColorInt int textColor) { 387 } 388 389 @Override 390 public void openPanel(int featureId, KeyEvent event) { 391 } 392 393 @Override 394 public void closePanel(int featureId) { 395 } 396 397 @Override 398 public void togglePanel(int featureId, KeyEvent event) { 399 } 400 401 @Override 402 public void invalidatePanelMenu(int featureId) { 403 } 404 405 @Override 406 public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) { 407 return false; 408 } 409 410 @Override 411 public boolean performPanelIdentifierAction(int featureId, int id, int flags) { 412 return false; 413 } 414 415 @Override 416 public void closeAllPanels() { 417 } 418 419 @Override 420 public boolean performContextMenuIdentifierAction(int id, int flags) { 421 return false; 422 } 423 424 @Override 425 public void onConfigurationChanged(Configuration newConfig) { 426 } 427 428 @Override 429 public void setBackgroundDrawable(Drawable drawable) { 430 } 431 432 @Override 433 public void setFeatureDrawableResource(int featureId, @DrawableRes int resId) { 434 } 435 436 @Override 437 public void setFeatureDrawableUri(int featureId, Uri uri) { 438 } 439 440 @Override 441 public void setFeatureDrawable(int featureId, Drawable drawable) { 442 } 443 444 @Override 445 public void setFeatureDrawableAlpha(int featureId, int alpha) { 446 } 447 448 @Override 449 public void setFeatureInt(int featureId, int value) { 450 } 451 452 @Override 453 public void takeKeyEvents(boolean get) { 454 } 455 456 @Override 457 public boolean superDispatchKeyEvent(KeyEvent event) { 458 return false; 459 } 460 461 @Override 462 public boolean superDispatchKeyShortcutEvent(KeyEvent event) { 463 return false; 464 } 465 466 @Override 467 public boolean superDispatchTouchEvent(MotionEvent event) { 468 return false; 469 } 470 471 @Override 472 public boolean superDispatchTrackballEvent(MotionEvent event) { 473 return false; 474 } 475 476 @Override 477 public boolean superDispatchGenericMotionEvent(MotionEvent event) { 478 return false; 479 } 480 481 @Override 482 public View getDecorView() { 483 return NotificationShadeWindowView.this; 484 } 485 486 @Override 487 public View peekDecorView() { 488 return null; 489 } 490 491 @Override 492 public Bundle saveHierarchyState() { 493 return null; 494 } 495 496 @Override 497 public void restoreHierarchyState(Bundle savedInstanceState) { 498 } 499 500 @Override 501 protected void onActive() { 502 } 503 504 @Override 505 public void setChildDrawable(int featureId, Drawable drawable) { 506 } 507 508 @Override 509 public void setChildInt(int featureId, int value) { 510 } 511 512 @Override 513 public boolean isShortcutKey(int keyCode, KeyEvent event) { 514 return false; 515 } 516 517 @Override 518 public void setVolumeControlStream(int streamType) { 519 } 520 521 @Override 522 public int getVolumeControlStream() { 523 return 0; 524 } 525 526 @Override 527 public int getStatusBarColor() { 528 return 0; 529 } 530 531 @Override 532 public void setStatusBarColor(@ColorInt int color) { 533 } 534 535 @Override 536 public int getNavigationBarColor() { 537 return 0; 538 } 539 540 @Override 541 public void setNavigationBarColor(@ColorInt int color) { 542 } 543 544 @Override 545 public void setDecorCaptionShade(int decorCaptionShade) { 546 } 547 548 @Override 549 public void setResizingCaptionDrawable(Drawable drawable) { 550 } 551 552 @Override 553 public void onMultiWindowModeChanged() { 554 } 555 556 @Override 557 public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) { 558 } 559 560 @Override 561 public WindowInsetsController getInsetsController() { 562 return null; 563 } 564 }; 565 566 } 567 568