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.WindowInsets.Type.systemBars; 21 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.annotation.Nullable; 28 import android.content.Context; 29 import android.content.res.Configuration; 30 import android.content.res.TypedArray; 31 import android.graphics.Canvas; 32 import android.graphics.Insets; 33 import android.graphics.Paint; 34 import android.graphics.Rect; 35 import android.graphics.drawable.Drawable; 36 import android.net.Uri; 37 import android.os.Bundle; 38 import android.os.Trace; 39 import android.util.AttributeSet; 40 import android.util.Pair; 41 import android.view.ActionMode; 42 import android.view.DisplayCutout; 43 import android.view.InputQueue; 44 import android.view.KeyEvent; 45 import android.view.LayoutInflater; 46 import android.view.Menu; 47 import android.view.MenuItem; 48 import android.view.MotionEvent; 49 import android.view.SurfaceHolder; 50 import android.view.View; 51 import android.view.ViewGroup; 52 import android.view.ViewTreeObserver; 53 import android.view.Window; 54 import android.view.WindowInsets; 55 import android.view.WindowInsetsController; 56 import android.widget.FrameLayout; 57 58 import com.android.internal.view.FloatingActionMode; 59 import com.android.internal.widget.floatingtoolbar.FloatingToolbar; 60 import com.android.systemui.R; 61 import com.android.systemui.compose.ComposeFacade; 62 63 /** 64 * Combined keyguard and notification panel view. Also holding backdrop and scrims. 65 */ 66 public class NotificationShadeWindowView extends FrameLayout { 67 public static final String TAG = "NotificationShadeWindowView"; 68 69 private int mRightInset = 0; 70 private int mLeftInset = 0; 71 72 // Implements the floating action mode for TextView's Cut/Copy/Past menu. Normally provided by 73 // DecorView, but since this is a special window we have to roll our own. 74 private View mFloatingActionModeOriginatingView; 75 private ActionMode mFloatingActionMode; 76 private FloatingToolbar mFloatingToolbar; 77 private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener; 78 79 private InteractionEventHandler mInteractionEventHandler; 80 private LayoutInsetsController mLayoutInsetProvider; 81 NotificationShadeWindowView(Context context, AttributeSet attrs)82 public NotificationShadeWindowView(Context context, AttributeSet attrs) { 83 super(context, attrs); 84 setMotionEventSplittingEnabled(false); 85 } 86 getNotificationPanelView()87 public NotificationPanelView getNotificationPanelView() { 88 return findViewById(R.id.notification_panel); 89 } 90 91 @Override onApplyWindowInsets(WindowInsets windowInsets)92 public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) { 93 final Insets insets = windowInsets.getInsetsIgnoringVisibility(systemBars()); 94 if (getFitsSystemWindows()) { 95 boolean paddingChanged = insets.top != getPaddingTop() 96 || insets.bottom != getPaddingBottom(); 97 98 // Drop top inset, and pass through bottom inset. 99 if (paddingChanged) { 100 setPadding(0, 0, 0, 0); 101 } 102 } else { 103 boolean changed = getPaddingLeft() != 0 104 || getPaddingRight() != 0 105 || getPaddingTop() != 0 106 || getPaddingBottom() != 0; 107 if (changed) { 108 setPadding(0, 0, 0, 0); 109 } 110 } 111 112 mLeftInset = 0; 113 mRightInset = 0; 114 DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout(); 115 Pair<Integer, Integer> pairInsets = mLayoutInsetProvider 116 .getinsets(windowInsets, displayCutout); 117 mLeftInset = pairInsets.first; 118 mRightInset = pairInsets.second; 119 applyMargins(); 120 return windowInsets; 121 } 122 applyMargins()123 private void applyMargins() { 124 final int count = getChildCount(); 125 for (int i = 0; i < count; i++) { 126 View child = getChildAt(i); 127 if (child.getLayoutParams() instanceof LayoutParams) { 128 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 129 if (!lp.ignoreRightInset 130 && (lp.rightMargin != mRightInset || lp.leftMargin != mLeftInset)) { 131 lp.rightMargin = mRightInset; 132 lp.leftMargin = mLeftInset; 133 child.requestLayout(); 134 } 135 } 136 } 137 } 138 139 @Override generateLayoutParams(AttributeSet attrs)140 public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) { 141 return new LayoutParams(getContext(), attrs); 142 } 143 144 @Override generateDefaultLayoutParams()145 protected FrameLayout.LayoutParams generateDefaultLayoutParams() { 146 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 147 } 148 149 @Override onAttachedToWindow()150 protected void onAttachedToWindow() { 151 super.onAttachedToWindow(); 152 setWillNotDraw(!DEBUG); 153 154 if (ComposeFacade.INSTANCE.isComposeAvailable()) { 155 ComposeFacade.INSTANCE.composeInitializer().onAttachedToWindow(this); 156 } 157 } 158 159 @Override onDetachedFromWindow()160 protected void onDetachedFromWindow() { 161 super.onDetachedFromWindow(); 162 if (ComposeFacade.INSTANCE.isComposeAvailable()) { 163 ComposeFacade.INSTANCE.composeInitializer().onDetachedFromWindow(this); 164 } 165 } 166 167 @Override dispatchKeyEvent(KeyEvent event)168 public boolean dispatchKeyEvent(KeyEvent event) { 169 if (mInteractionEventHandler.interceptMediaKey(event)) { 170 return true; 171 } 172 173 if (super.dispatchKeyEvent(event)) { 174 return true; 175 } 176 177 return mInteractionEventHandler.dispatchKeyEvent(event); 178 } 179 180 @Override dispatchKeyEventPreIme(KeyEvent event)181 public boolean dispatchKeyEventPreIme(KeyEvent event) { 182 return mInteractionEventHandler.dispatchKeyEventPreIme(event); 183 } 184 setInteractionEventHandler(InteractionEventHandler listener)185 protected void setInteractionEventHandler(InteractionEventHandler listener) { 186 mInteractionEventHandler = listener; 187 } 188 setLayoutInsetsController(LayoutInsetsController provider)189 protected void setLayoutInsetsController(LayoutInsetsController provider) { 190 mLayoutInsetProvider = provider; 191 } 192 193 @Override dispatchTouchEvent(MotionEvent ev)194 public boolean dispatchTouchEvent(MotionEvent ev) { 195 Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev); 196 197 result = result != null ? result : super.dispatchTouchEvent(ev); 198 199 mInteractionEventHandler.dispatchTouchEventComplete(); 200 201 return result; 202 } 203 204 @Override onInterceptTouchEvent(MotionEvent ev)205 public boolean onInterceptTouchEvent(MotionEvent ev) { 206 boolean intercept = mInteractionEventHandler.shouldInterceptTouchEvent(ev); 207 if (!intercept) { 208 intercept = super.onInterceptTouchEvent(ev); 209 } 210 if (intercept) { 211 mInteractionEventHandler.didIntercept(ev); 212 } 213 214 return intercept; 215 } 216 217 @Override onTouchEvent(MotionEvent ev)218 public boolean onTouchEvent(MotionEvent ev) { 219 boolean handled = mInteractionEventHandler.handleTouchEvent(ev); 220 221 if (!handled) { 222 handled = super.onTouchEvent(ev); 223 } 224 225 if (!handled) { 226 mInteractionEventHandler.didNotHandleTouchEvent(ev); 227 } 228 229 return handled; 230 } 231 232 @Override onDraw(Canvas canvas)233 public void onDraw(Canvas canvas) { 234 super.onDraw(canvas); 235 if (DEBUG) { 236 Paint pt = new Paint(); 237 pt.setColor(0x80FFFF00); 238 pt.setStrokeWidth(12.0f); 239 pt.setStyle(Paint.Style.STROKE); 240 canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), pt); 241 } 242 } 243 244 private static class LayoutParams extends FrameLayout.LayoutParams { 245 246 public boolean ignoreRightInset; 247 LayoutParams(int width, int height)248 LayoutParams(int width, int height) { 249 super(width, height); 250 } 251 LayoutParams(Context c, AttributeSet attrs)252 LayoutParams(Context c, AttributeSet attrs) { 253 super(c, attrs); 254 255 TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout); 256 ignoreRightInset = a.getBoolean( 257 R.styleable.StatusBarWindowView_Layout_ignoreRightInset, false); 258 a.recycle(); 259 } 260 } 261 262 @Override startActionModeForChild(View originalView, ActionMode.Callback callback, int type)263 public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback, 264 int type) { 265 if (type == ActionMode.TYPE_FLOATING) { 266 return startActionMode(originalView, callback); 267 } 268 return super.startActionModeForChild(originalView, callback, type); 269 } 270 createFloatingActionMode( View originatingView, ActionMode.Callback2 callback)271 private ActionMode createFloatingActionMode( 272 View originatingView, ActionMode.Callback2 callback) { 273 if (mFloatingActionMode != null) { 274 mFloatingActionMode.finish(); 275 } 276 cleanupFloatingActionModeViews(); 277 mFloatingToolbar = new FloatingToolbar(mFakeWindow); 278 final FloatingActionMode mode = 279 new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar); 280 mFloatingActionModeOriginatingView = originatingView; 281 mFloatingToolbarPreDrawListener = () -> { 282 mode.updateViewLocationInWindow(); 283 return true; 284 }; 285 return mode; 286 } 287 setHandledFloatingActionMode(ActionMode mode)288 private void setHandledFloatingActionMode(ActionMode mode) { 289 mFloatingActionMode = mode; 290 mFloatingActionMode.invalidate(); // Will show the floating toolbar if necessary. 291 mFloatingActionModeOriginatingView.getViewTreeObserver() 292 .addOnPreDrawListener(mFloatingToolbarPreDrawListener); 293 } 294 cleanupFloatingActionModeViews()295 private void cleanupFloatingActionModeViews() { 296 if (mFloatingToolbar != null) { 297 mFloatingToolbar.dismiss(); 298 mFloatingToolbar = null; 299 } 300 if (mFloatingActionModeOriginatingView != null) { 301 if (mFloatingToolbarPreDrawListener != null) { 302 mFloatingActionModeOriginatingView.getViewTreeObserver() 303 .removeOnPreDrawListener(mFloatingToolbarPreDrawListener); 304 mFloatingToolbarPreDrawListener = null; 305 } 306 mFloatingActionModeOriginatingView = null; 307 } 308 } 309 startActionMode( View originatingView, ActionMode.Callback callback)310 private ActionMode startActionMode( 311 View originatingView, ActionMode.Callback callback) { 312 ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback); 313 ActionMode mode = createFloatingActionMode(originatingView, wrappedCallback); 314 if (wrappedCallback.onCreateActionMode(mode, mode.getMenu())) { 315 setHandledFloatingActionMode(mode); 316 } else { 317 mode = null; 318 } 319 return mode; 320 } 321 322 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)323 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 324 Trace.beginSection("NotificationShadeWindowView#onMeasure"); 325 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 326 Trace.endSection(); 327 } 328 329 @Override requestLayout()330 public void requestLayout() { 331 Trace.instant(TRACE_TAG_APP, "NotificationShadeWindowView#requestLayout"); 332 super.requestLayout(); 333 } 334 335 private class ActionModeCallback2Wrapper extends ActionMode.Callback2 { 336 private final ActionMode.Callback mWrapped; 337 ActionModeCallback2Wrapper(ActionMode.Callback wrapped)338 ActionModeCallback2Wrapper(ActionMode.Callback wrapped) { 339 mWrapped = wrapped; 340 } 341 onCreateActionMode(ActionMode mode, Menu menu)342 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 343 return mWrapped.onCreateActionMode(mode, menu); 344 } 345 onPrepareActionMode(ActionMode mode, Menu menu)346 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 347 requestFitSystemWindows(); 348 return mWrapped.onPrepareActionMode(mode, menu); 349 } 350 onActionItemClicked(ActionMode mode, MenuItem item)351 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 352 return mWrapped.onActionItemClicked(mode, item); 353 } 354 onDestroyActionMode(ActionMode mode)355 public void onDestroyActionMode(ActionMode mode) { 356 mWrapped.onDestroyActionMode(mode); 357 if (mode == mFloatingActionMode) { 358 cleanupFloatingActionModeViews(); 359 mFloatingActionMode = null; 360 } 361 requestFitSystemWindows(); 362 } 363 364 @Override onGetContentRect(ActionMode mode, View view, Rect outRect)365 public void onGetContentRect(ActionMode mode, View view, Rect outRect) { 366 if (mWrapped instanceof ActionMode.Callback2) { 367 ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect); 368 } else { 369 super.onGetContentRect(mode, view, outRect); 370 } 371 } 372 } 373 374 /** 375 * Controller responsible for calculating insets for the shade window. 376 */ 377 public interface LayoutInsetsController { 378 379 /** 380 * Update the insets and calculate them accordingly. 381 */ getinsets(@ullable WindowInsets windowInsets, @Nullable DisplayCutout displayCutout)382 Pair<Integer, Integer> getinsets(@Nullable WindowInsets windowInsets, 383 @Nullable DisplayCutout displayCutout); 384 } 385 386 interface InteractionEventHandler { 387 /** 388 * Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer 389 * to the super method. 390 */ handleDispatchTouchEvent(MotionEvent ev)391 Boolean handleDispatchTouchEvent(MotionEvent ev); 392 393 /** 394 * Called after all dispatching is done. 395 */ 396 dispatchTouchEventComplete()397 void dispatchTouchEventComplete(); 398 399 /** 400 * Returns if the view should intercept the touch event. 401 * 402 * The touch event may still be interecepted if 403 * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)} decides to do so. 404 */ shouldInterceptTouchEvent(MotionEvent ev)405 boolean shouldInterceptTouchEvent(MotionEvent ev); 406 407 /** 408 * Called when the view decides to intercept the touch event. 409 */ didIntercept(MotionEvent ev)410 void didIntercept(MotionEvent ev); 411 handleTouchEvent(MotionEvent ev)412 boolean handleTouchEvent(MotionEvent ev); 413 didNotHandleTouchEvent(MotionEvent ev)414 void didNotHandleTouchEvent(MotionEvent ev); 415 interceptMediaKey(KeyEvent event)416 boolean interceptMediaKey(KeyEvent event); 417 dispatchKeyEvent(KeyEvent event)418 boolean dispatchKeyEvent(KeyEvent event); 419 dispatchKeyEventPreIme(KeyEvent event)420 boolean dispatchKeyEventPreIme(KeyEvent event); 421 } 422 423 /** 424 * Minimal window to satisfy FloatingToolbar. 425 */ 426 private final Window mFakeWindow = new Window(mContext) { 427 @Override 428 public void takeSurface(SurfaceHolder.Callback2 callback) { 429 } 430 431 @Override 432 public void takeInputQueue(InputQueue.Callback callback) { 433 } 434 435 @Override 436 public boolean isFloating() { 437 return false; 438 } 439 440 @Override 441 public void alwaysReadCloseOnTouchAttr() { 442 } 443 444 @Override 445 public void setContentView(@LayoutRes int layoutResID) { 446 } 447 448 @Override 449 public void setContentView(View view) { 450 } 451 452 @Override 453 public void setContentView(View view, ViewGroup.LayoutParams params) { 454 } 455 456 @Override 457 public void addContentView(View view, ViewGroup.LayoutParams params) { 458 } 459 460 @Override 461 public void clearContentView() { 462 } 463 464 @Override 465 public View getCurrentFocus() { 466 return null; 467 } 468 469 @Override 470 public LayoutInflater getLayoutInflater() { 471 return null; 472 } 473 474 @Override 475 public void setTitle(CharSequence title) { 476 } 477 478 @Override 479 public void setTitleColor(@ColorInt int textColor) { 480 } 481 482 @Override 483 public void openPanel(int featureId, KeyEvent event) { 484 } 485 486 @Override 487 public void closePanel(int featureId) { 488 } 489 490 @Override 491 public void togglePanel(int featureId, KeyEvent event) { 492 } 493 494 @Override 495 public void invalidatePanelMenu(int featureId) { 496 } 497 498 @Override 499 public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) { 500 return false; 501 } 502 503 @Override 504 public boolean performPanelIdentifierAction(int featureId, int id, int flags) { 505 return false; 506 } 507 508 @Override 509 public void closeAllPanels() { 510 } 511 512 @Override 513 public boolean performContextMenuIdentifierAction(int id, int flags) { 514 return false; 515 } 516 517 @Override 518 public void onConfigurationChanged(Configuration newConfig) { 519 } 520 521 @Override 522 public void setBackgroundDrawable(Drawable drawable) { 523 } 524 525 @Override 526 public void setFeatureDrawableResource(int featureId, @DrawableRes int resId) { 527 } 528 529 @Override 530 public void setFeatureDrawableUri(int featureId, Uri uri) { 531 } 532 533 @Override 534 public void setFeatureDrawable(int featureId, Drawable drawable) { 535 } 536 537 @Override 538 public void setFeatureDrawableAlpha(int featureId, int alpha) { 539 } 540 541 @Override 542 public void setFeatureInt(int featureId, int value) { 543 } 544 545 @Override 546 public void takeKeyEvents(boolean get) { 547 } 548 549 @Override 550 public boolean superDispatchKeyEvent(KeyEvent event) { 551 return false; 552 } 553 554 @Override 555 public boolean superDispatchKeyShortcutEvent(KeyEvent event) { 556 return false; 557 } 558 559 @Override 560 public boolean superDispatchTouchEvent(MotionEvent event) { 561 return false; 562 } 563 564 @Override 565 public boolean superDispatchTrackballEvent(MotionEvent event) { 566 return false; 567 } 568 569 @Override 570 public boolean superDispatchGenericMotionEvent(MotionEvent event) { 571 return false; 572 } 573 574 @Override 575 public View getDecorView() { 576 return NotificationShadeWindowView.this; 577 } 578 579 @Override 580 public View peekDecorView() { 581 return null; 582 } 583 584 @Override 585 public Bundle saveHierarchyState() { 586 return null; 587 } 588 589 @Override 590 public void restoreHierarchyState(Bundle savedInstanceState) { 591 } 592 593 @Override 594 protected void onActive() { 595 } 596 597 @Override 598 public void setChildDrawable(int featureId, Drawable drawable) { 599 } 600 601 @Override 602 public void setChildInt(int featureId, int value) { 603 } 604 605 @Override 606 public boolean isShortcutKey(int keyCode, KeyEvent event) { 607 return false; 608 } 609 610 @Override 611 public void setVolumeControlStream(int streamType) { 612 } 613 614 @Override 615 public int getVolumeControlStream() { 616 return 0; 617 } 618 619 @Override 620 public int getStatusBarColor() { 621 return 0; 622 } 623 624 @Override 625 public void setStatusBarColor(@ColorInt int color) { 626 } 627 628 @Override 629 public int getNavigationBarColor() { 630 return 0; 631 } 632 633 @Override 634 public void setNavigationBarColor(@ColorInt int color) { 635 } 636 637 @Override 638 public void setDecorCaptionShade(int decorCaptionShade) { 639 } 640 641 @Override 642 public void setResizingCaptionDrawable(Drawable drawable) { 643 } 644 645 @Override 646 public void onMultiWindowModeChanged() { 647 } 648 649 @Override 650 public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) { 651 } 652 653 @Override 654 public void reportActivityRelaunched() { 655 } 656 657 @Override 658 public WindowInsetsController getInsetsController() { 659 return null; 660 } 661 }; 662 663 } 664 665