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