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 setInteractionEventHandler(InteractionEventHandler listener)163 protected void setInteractionEventHandler(InteractionEventHandler listener) { 164 mInteractionEventHandler = listener; 165 } 166 167 @Override dispatchTouchEvent(MotionEvent ev)168 public boolean dispatchTouchEvent(MotionEvent ev) { 169 Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev); 170 171 return result != null ? result : super.dispatchTouchEvent(ev); 172 } 173 174 @Override onInterceptTouchEvent(MotionEvent ev)175 public boolean onInterceptTouchEvent(MotionEvent ev) { 176 boolean intercept = mInteractionEventHandler.shouldInterceptTouchEvent(ev); 177 if (!intercept) { 178 intercept = super.onInterceptTouchEvent(ev); 179 } 180 if (intercept) { 181 mInteractionEventHandler.didIntercept(ev); 182 } 183 184 return intercept; 185 } 186 187 @Override onTouchEvent(MotionEvent ev)188 public boolean onTouchEvent(MotionEvent ev) { 189 boolean handled = mInteractionEventHandler.handleTouchEvent(ev); 190 191 if (!handled) { 192 handled = super.onTouchEvent(ev); 193 } 194 195 if (!handled) { 196 mInteractionEventHandler.didNotHandleTouchEvent(ev); 197 } 198 199 return handled; 200 } 201 202 @Override onDraw(Canvas canvas)203 public void onDraw(Canvas canvas) { 204 super.onDraw(canvas); 205 if (DEBUG) { 206 Paint pt = new Paint(); 207 pt.setColor(0x80FFFF00); 208 pt.setStrokeWidth(12.0f); 209 pt.setStyle(Paint.Style.STROKE); 210 canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), pt); 211 } 212 } 213 214 class LayoutParams extends FrameLayout.LayoutParams { 215 216 public boolean ignoreRightInset; 217 LayoutParams(int width, int height)218 LayoutParams(int width, int height) { 219 super(width, height); 220 } 221 LayoutParams(Context c, AttributeSet attrs)222 LayoutParams(Context c, AttributeSet attrs) { 223 super(c, attrs); 224 225 TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout); 226 ignoreRightInset = a.getBoolean( 227 R.styleable.StatusBarWindowView_Layout_ignoreRightInset, false); 228 a.recycle(); 229 } 230 } 231 232 @Override startActionModeForChild(View originalView, ActionMode.Callback callback, int type)233 public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback, 234 int type) { 235 if (type == ActionMode.TYPE_FLOATING) { 236 return startActionMode(originalView, callback, type); 237 } 238 return super.startActionModeForChild(originalView, callback, type); 239 } 240 createFloatingActionMode( View originatingView, ActionMode.Callback2 callback)241 private ActionMode createFloatingActionMode( 242 View originatingView, ActionMode.Callback2 callback) { 243 if (mFloatingActionMode != null) { 244 mFloatingActionMode.finish(); 245 } 246 cleanupFloatingActionModeViews(); 247 mFloatingToolbar = new FloatingToolbar(mFakeWindow); 248 final FloatingActionMode mode = 249 new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar); 250 mFloatingActionModeOriginatingView = originatingView; 251 mFloatingToolbarPreDrawListener = 252 new ViewTreeObserver.OnPreDrawListener() { 253 @Override 254 public boolean onPreDraw() { 255 mode.updateViewLocationInWindow(); 256 return true; 257 } 258 }; 259 return mode; 260 } 261 setHandledFloatingActionMode(ActionMode mode)262 private void setHandledFloatingActionMode(ActionMode mode) { 263 mFloatingActionMode = mode; 264 mFloatingActionMode.invalidate(); // Will show the floating toolbar if necessary. 265 mFloatingActionModeOriginatingView.getViewTreeObserver() 266 .addOnPreDrawListener(mFloatingToolbarPreDrawListener); 267 } 268 cleanupFloatingActionModeViews()269 private void cleanupFloatingActionModeViews() { 270 if (mFloatingToolbar != null) { 271 mFloatingToolbar.dismiss(); 272 mFloatingToolbar = null; 273 } 274 if (mFloatingActionModeOriginatingView != null) { 275 if (mFloatingToolbarPreDrawListener != null) { 276 mFloatingActionModeOriginatingView.getViewTreeObserver() 277 .removeOnPreDrawListener(mFloatingToolbarPreDrawListener); 278 mFloatingToolbarPreDrawListener = null; 279 } 280 mFloatingActionModeOriginatingView = null; 281 } 282 } 283 startActionMode( View originatingView, ActionMode.Callback callback, int type)284 private ActionMode startActionMode( 285 View originatingView, ActionMode.Callback callback, int type) { 286 ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback); 287 ActionMode mode = createFloatingActionMode(originatingView, wrappedCallback); 288 if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) { 289 setHandledFloatingActionMode(mode); 290 } else { 291 mode = null; 292 } 293 return mode; 294 } 295 296 private class ActionModeCallback2Wrapper extends ActionMode.Callback2 { 297 private final ActionMode.Callback mWrapped; 298 ActionModeCallback2Wrapper(ActionMode.Callback wrapped)299 ActionModeCallback2Wrapper(ActionMode.Callback wrapped) { 300 mWrapped = wrapped; 301 } 302 onCreateActionMode(ActionMode mode, Menu menu)303 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 304 return mWrapped.onCreateActionMode(mode, menu); 305 } 306 onPrepareActionMode(ActionMode mode, Menu menu)307 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 308 requestFitSystemWindows(); 309 return mWrapped.onPrepareActionMode(mode, menu); 310 } 311 onActionItemClicked(ActionMode mode, MenuItem item)312 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 313 return mWrapped.onActionItemClicked(mode, item); 314 } 315 onDestroyActionMode(ActionMode mode)316 public void onDestroyActionMode(ActionMode mode) { 317 mWrapped.onDestroyActionMode(mode); 318 if (mode == mFloatingActionMode) { 319 cleanupFloatingActionModeViews(); 320 mFloatingActionMode = null; 321 } 322 requestFitSystemWindows(); 323 } 324 325 @Override onGetContentRect(ActionMode mode, View view, Rect outRect)326 public void onGetContentRect(ActionMode mode, View view, Rect outRect) { 327 if (mWrapped instanceof ActionMode.Callback2) { 328 ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect); 329 } else { 330 super.onGetContentRect(mode, view, outRect); 331 } 332 } 333 } 334 335 interface InteractionEventHandler { 336 /** 337 * Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer 338 * to the super method. 339 */ handleDispatchTouchEvent(MotionEvent ev)340 Boolean handleDispatchTouchEvent(MotionEvent ev); 341 342 /** 343 * Returns if the view should intercept the touch event. 344 * 345 * The touch event may still be interecepted if 346 * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)} decides to do so. 347 */ shouldInterceptTouchEvent(MotionEvent ev)348 boolean shouldInterceptTouchEvent(MotionEvent ev); 349 350 /** 351 * Called when the view decides to intercept the touch event. 352 */ didIntercept(MotionEvent ev)353 void didIntercept(MotionEvent ev); 354 handleTouchEvent(MotionEvent ev)355 boolean handleTouchEvent(MotionEvent ev); 356 didNotHandleTouchEvent(MotionEvent ev)357 void didNotHandleTouchEvent(MotionEvent ev); 358 interceptMediaKey(KeyEvent event)359 boolean interceptMediaKey(KeyEvent event); 360 dispatchKeyEvent(KeyEvent event)361 boolean dispatchKeyEvent(KeyEvent event); 362 } 363 364 /** 365 * Minimal window to satisfy FloatingToolbar. 366 */ 367 private Window mFakeWindow = new Window(mContext) { 368 @Override 369 public void takeSurface(SurfaceHolder.Callback2 callback) { 370 } 371 372 @Override 373 public void takeInputQueue(InputQueue.Callback callback) { 374 } 375 376 @Override 377 public boolean isFloating() { 378 return false; 379 } 380 381 @Override 382 public void alwaysReadCloseOnTouchAttr() { 383 } 384 385 @Override 386 public void setContentView(@LayoutRes int layoutResID) { 387 } 388 389 @Override 390 public void setContentView(View view) { 391 } 392 393 @Override 394 public void setContentView(View view, ViewGroup.LayoutParams params) { 395 } 396 397 @Override 398 public void addContentView(View view, ViewGroup.LayoutParams params) { 399 } 400 401 @Override 402 public void clearContentView() { 403 } 404 405 @Override 406 public View getCurrentFocus() { 407 return null; 408 } 409 410 @Override 411 public LayoutInflater getLayoutInflater() { 412 return null; 413 } 414 415 @Override 416 public void setTitle(CharSequence title) { 417 } 418 419 @Override 420 public void setTitleColor(@ColorInt int textColor) { 421 } 422 423 @Override 424 public void openPanel(int featureId, KeyEvent event) { 425 } 426 427 @Override 428 public void closePanel(int featureId) { 429 } 430 431 @Override 432 public void togglePanel(int featureId, KeyEvent event) { 433 } 434 435 @Override 436 public void invalidatePanelMenu(int featureId) { 437 } 438 439 @Override 440 public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) { 441 return false; 442 } 443 444 @Override 445 public boolean performPanelIdentifierAction(int featureId, int id, int flags) { 446 return false; 447 } 448 449 @Override 450 public void closeAllPanels() { 451 } 452 453 @Override 454 public boolean performContextMenuIdentifierAction(int id, int flags) { 455 return false; 456 } 457 458 @Override 459 public void onConfigurationChanged(Configuration newConfig) { 460 } 461 462 @Override 463 public void setBackgroundDrawable(Drawable drawable) { 464 } 465 466 @Override 467 public void setFeatureDrawableResource(int featureId, @DrawableRes int resId) { 468 } 469 470 @Override 471 public void setFeatureDrawableUri(int featureId, Uri uri) { 472 } 473 474 @Override 475 public void setFeatureDrawable(int featureId, Drawable drawable) { 476 } 477 478 @Override 479 public void setFeatureDrawableAlpha(int featureId, int alpha) { 480 } 481 482 @Override 483 public void setFeatureInt(int featureId, int value) { 484 } 485 486 @Override 487 public void takeKeyEvents(boolean get) { 488 } 489 490 @Override 491 public boolean superDispatchKeyEvent(KeyEvent event) { 492 return false; 493 } 494 495 @Override 496 public boolean superDispatchKeyShortcutEvent(KeyEvent event) { 497 return false; 498 } 499 500 @Override 501 public boolean superDispatchTouchEvent(MotionEvent event) { 502 return false; 503 } 504 505 @Override 506 public boolean superDispatchTrackballEvent(MotionEvent event) { 507 return false; 508 } 509 510 @Override 511 public boolean superDispatchGenericMotionEvent(MotionEvent event) { 512 return false; 513 } 514 515 @Override 516 public View getDecorView() { 517 return NotificationShadeWindowView.this; 518 } 519 520 @Override 521 public View peekDecorView() { 522 return null; 523 } 524 525 @Override 526 public Bundle saveHierarchyState() { 527 return null; 528 } 529 530 @Override 531 public void restoreHierarchyState(Bundle savedInstanceState) { 532 } 533 534 @Override 535 protected void onActive() { 536 } 537 538 @Override 539 public void setChildDrawable(int featureId, Drawable drawable) { 540 } 541 542 @Override 543 public void setChildInt(int featureId, int value) { 544 } 545 546 @Override 547 public boolean isShortcutKey(int keyCode, KeyEvent event) { 548 return false; 549 } 550 551 @Override 552 public void setVolumeControlStream(int streamType) { 553 } 554 555 @Override 556 public int getVolumeControlStream() { 557 return 0; 558 } 559 560 @Override 561 public int getStatusBarColor() { 562 return 0; 563 } 564 565 @Override 566 public void setStatusBarColor(@ColorInt int color) { 567 } 568 569 @Override 570 public int getNavigationBarColor() { 571 return 0; 572 } 573 574 @Override 575 public void setNavigationBarColor(@ColorInt int color) { 576 } 577 578 @Override 579 public void setDecorCaptionShade(int decorCaptionShade) { 580 } 581 582 @Override 583 public void setResizingCaptionDrawable(Drawable drawable) { 584 } 585 586 @Override 587 public void onMultiWindowModeChanged() { 588 } 589 590 @Override 591 public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) { 592 } 593 594 @Override 595 public void reportActivityRelaunched() { 596 } 597 598 @Override 599 public WindowInsetsController getInsetsController() { 600 return null; 601 } 602 }; 603 604 } 605 606