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