1 /* 2 * Copyright (C) 2012 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 android.annotation.ColorInt; 20 import android.annotation.DrawableRes; 21 import android.annotation.LayoutRes; 22 import android.app.StatusBarManager; 23 import android.content.Context; 24 import android.content.res.Configuration; 25 import android.content.res.TypedArray; 26 import android.graphics.Canvas; 27 import android.graphics.Paint; 28 import android.graphics.PorterDuff; 29 import android.graphics.PorterDuffXfermode; 30 import android.graphics.Rect; 31 import android.graphics.drawable.Drawable; 32 import android.media.AudioManager; 33 import android.media.session.MediaSessionLegacyHelper; 34 import android.net.Uri; 35 import android.os.Bundle; 36 import android.os.IBinder; 37 import android.os.SystemClock; 38 import android.util.AttributeSet; 39 import android.view.ActionMode; 40 import android.view.InputDevice; 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.WindowManager; 53 import android.view.WindowManagerGlobal; 54 import android.widget.FrameLayout; 55 56 import com.android.internal.annotations.VisibleForTesting; 57 import com.android.internal.view.FloatingActionMode; 58 import com.android.internal.widget.FloatingToolbar; 59 import com.android.systemui.R; 60 import com.android.systemui.classifier.FalsingManager; 61 import com.android.systemui.statusbar.DragDownHelper; 62 import com.android.systemui.statusbar.StatusBarState; 63 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 64 65 66 public class StatusBarWindowView extends FrameLayout { 67 public static final String TAG = "StatusBarWindowView"; 68 public static final boolean DEBUG = StatusBar.DEBUG; 69 70 private DragDownHelper mDragDownHelper; 71 private DoubleTapHelper mDoubleTapHelper; 72 private NotificationStackScrollLayout mStackScrollLayout; 73 private NotificationPanelView mNotificationPanel; 74 private View mBrightnessMirror; 75 76 private int mRightInset = 0; 77 private int mLeftInset = 0; 78 79 private StatusBar mService; 80 private final Paint mTransparentSrcPaint = new Paint(); 81 private FalsingManager mFalsingManager; 82 83 // Implements the floating action mode for TextView's Cut/Copy/Past menu. Normally provided by 84 // DecorView, but since this is a special window we have to roll our own. 85 private View mFloatingActionModeOriginatingView; 86 private ActionMode mFloatingActionMode; 87 private FloatingToolbar mFloatingToolbar; 88 private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener; 89 private boolean mTouchCancelled; 90 private boolean mTouchActive; 91 92 public StatusBarWindowView(Context context, AttributeSet attrs) { 93 super(context, attrs); 94 setMotionEventSplittingEnabled(false); 95 mTransparentSrcPaint.setColor(0); 96 mTransparentSrcPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); 97 mFalsingManager = FalsingManager.getInstance(context); 98 mDoubleTapHelper = new DoubleTapHelper(this, active -> {}, () -> { 99 mService.wakeUpIfDozing(SystemClock.uptimeMillis(), this); 100 return true; 101 }, null, null); 102 } 103 104 @Override 105 protected boolean fitSystemWindows(Rect insets) { 106 if (getFitsSystemWindows()) { 107 boolean paddingChanged = insets.top != getPaddingTop() 108 || insets.bottom != getPaddingBottom(); 109 110 // Super-special right inset handling, because scrims and backdrop need to ignore it. 111 if (insets.right != mRightInset || insets.left != mLeftInset) { 112 mRightInset = insets.right; 113 mLeftInset = insets.left; 114 applyMargins(); 115 } 116 // Drop top inset, and pass through bottom inset. 117 if (paddingChanged) { 118 setPadding(0, 0, 0, 0); 119 } 120 insets.left = 0; 121 insets.top = 0; 122 insets.right = 0; 123 } else { 124 if (mRightInset != 0 || mLeftInset != 0) { 125 mRightInset = 0; 126 mLeftInset = 0; 127 applyMargins(); 128 } 129 boolean changed = getPaddingLeft() != 0 130 || getPaddingRight() != 0 131 || getPaddingTop() != 0 132 || getPaddingBottom() != 0; 133 if (changed) { 134 setPadding(0, 0, 0, 0); 135 } 136 insets.top = 0; 137 } 138 return false; 139 } 140 141 private void applyMargins() { 142 final int N = getChildCount(); 143 for (int i = 0; i < N; i++) { 144 View child = getChildAt(i); 145 if (child.getLayoutParams() instanceof LayoutParams) { 146 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 147 if (!lp.ignoreRightInset 148 && (lp.rightMargin != mRightInset || lp.leftMargin != mLeftInset)) { 149 lp.rightMargin = mRightInset; 150 lp.leftMargin = mLeftInset; 151 child.requestLayout(); 152 } 153 } 154 } 155 } 156 157 @Override 158 public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) { 159 return new LayoutParams(getContext(), attrs); 160 } 161 162 @Override 163 protected FrameLayout.LayoutParams generateDefaultLayoutParams() { 164 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 165 } 166 167 @Override 168 protected void onFinishInflate() { 169 super.onFinishInflate(); 170 mStackScrollLayout = (NotificationStackScrollLayout) findViewById( 171 R.id.notification_stack_scroller); 172 mNotificationPanel = (NotificationPanelView) findViewById(R.id.notification_panel); 173 mBrightnessMirror = findViewById(R.id.brightness_mirror); 174 } 175 176 @Override 177 public void onViewAdded(View child) { 178 super.onViewAdded(child); 179 if (child.getId() == R.id.brightness_mirror) { 180 mBrightnessMirror = child; 181 } 182 } 183 184 public void setService(StatusBar service) { 185 mService = service; 186 setDragDownHelper(new DragDownHelper(getContext(), this, mStackScrollLayout, mService)); 187 } 188 189 @VisibleForTesting 190 void setDragDownHelper(DragDownHelper dragDownHelper) { 191 mDragDownHelper = dragDownHelper; 192 } 193 194 @Override 195 protected void onAttachedToWindow () { 196 super.onAttachedToWindow(); 197 198 // We need to ensure that our window doesn't suffer from overdraw which would normally 199 // occur if our window is translucent. Since we are drawing the whole window anyway with 200 // the scrim, we don't need the window to be cleared in the beginning. 201 if (mService.isScrimSrcModeEnabled()) { 202 IBinder windowToken = getWindowToken(); 203 WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams(); 204 lp.token = windowToken; 205 setLayoutParams(lp); 206 WindowManagerGlobal.getInstance().changeCanvasOpacity(windowToken, true); 207 setWillNotDraw(false); 208 } else { 209 setWillNotDraw(!DEBUG); 210 } 211 } 212 213 @Override 214 public boolean dispatchKeyEvent(KeyEvent event) { 215 if (mService.interceptMediaKey(event)) { 216 return true; 217 } 218 if (super.dispatchKeyEvent(event)) { 219 return true; 220 } 221 boolean down = event.getAction() == KeyEvent.ACTION_DOWN; 222 switch (event.getKeyCode()) { 223 case KeyEvent.KEYCODE_BACK: 224 if (!down) { 225 mService.onBackPressed(); 226 } 227 return true; 228 case KeyEvent.KEYCODE_MENU: 229 if (!down) { 230 return mService.onMenuPressed(); 231 } 232 case KeyEvent.KEYCODE_SPACE: 233 if (!down) { 234 return mService.onSpacePressed(); 235 } 236 break; 237 case KeyEvent.KEYCODE_VOLUME_DOWN: 238 case KeyEvent.KEYCODE_VOLUME_UP: 239 if (mService.isDozing()) { 240 MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent( 241 event, AudioManager.USE_DEFAULT_STREAM_TYPE, true); 242 return true; 243 } 244 break; 245 } 246 return false; 247 } 248 249 public void setTouchActive(boolean touchActive) { 250 mTouchActive = touchActive; 251 mStackScrollLayout.setTouchActive(touchActive); 252 } 253 254 @Override 255 public boolean dispatchTouchEvent(MotionEvent ev) { 256 boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN; 257 boolean isCancel = ev.getActionMasked() == MotionEvent.ACTION_CANCEL; 258 if (!isCancel && mService.shouldIgnoreTouch()) { 259 return false; 260 } 261 if (isDown && mNotificationPanel.isFullyCollapsed()) { 262 mNotificationPanel.startExpandLatencyTracking(); 263 } 264 if (isDown) { 265 setTouchActive(true); 266 mTouchCancelled = false; 267 } else if (ev.getActionMasked() == MotionEvent.ACTION_UP 268 || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) { 269 setTouchActive(false); 270 } 271 if (mTouchCancelled) { 272 return false; 273 } 274 mFalsingManager.onTouchEvent(ev, getWidth(), getHeight()); 275 if (mBrightnessMirror != null && mBrightnessMirror.getVisibility() == VISIBLE) { 276 // Disallow new pointers while the brightness mirror is visible. This is so that you 277 // can't touch anything other than the brightness slider while the mirror is showing 278 // and the rest of the panel is transparent. 279 if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { 280 return false; 281 } 282 } 283 if (isDown) { 284 mStackScrollLayout.closeControlsIfOutsideTouch(ev); 285 } 286 if (mService.isDozing()) { 287 mService.mDozeScrimController.extendPulse(); 288 } 289 290 return super.dispatchTouchEvent(ev); 291 } 292 293 @Override 294 public boolean onInterceptTouchEvent(MotionEvent ev) { 295 if (mService.isDozing() && !mStackScrollLayout.hasPulsingNotifications()) { 296 // Capture all touch events in always-on. 297 return true; 298 } 299 boolean intercept = false; 300 if (mNotificationPanel.isFullyExpanded() 301 && mStackScrollLayout.getVisibility() == View.VISIBLE 302 && mService.getBarState() == StatusBarState.KEYGUARD 303 && !mService.isBouncerShowing() 304 && !mService.isDozing()) { 305 intercept = mDragDownHelper.onInterceptTouchEvent(ev); 306 } 307 if (!intercept) { 308 super.onInterceptTouchEvent(ev); 309 } 310 if (intercept) { 311 MotionEvent cancellation = MotionEvent.obtain(ev); 312 cancellation.setAction(MotionEvent.ACTION_CANCEL); 313 mStackScrollLayout.onInterceptTouchEvent(cancellation); 314 mNotificationPanel.onInterceptTouchEvent(cancellation); 315 cancellation.recycle(); 316 } 317 return intercept; 318 } 319 320 @Override 321 public boolean onTouchEvent(MotionEvent ev) { 322 boolean handled = false; 323 if (mService.isDozing()) { 324 mDoubleTapHelper.onTouchEvent(ev); 325 handled = true; 326 } 327 if ((mService.getBarState() == StatusBarState.KEYGUARD && !handled) 328 || mDragDownHelper.isDraggingDown()) { 329 // we still want to finish our drag down gesture when locking the screen 330 handled = mDragDownHelper.onTouchEvent(ev); 331 } 332 if (!handled) { 333 handled = super.onTouchEvent(ev); 334 } 335 final int action = ev.getAction(); 336 if (!handled && (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)) { 337 mService.setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); 338 } 339 return handled; 340 } 341 342 @Override 343 public void onDraw(Canvas canvas) { 344 super.onDraw(canvas); 345 if (mService.isScrimSrcModeEnabled()) { 346 // We need to ensure that our window is always drawn fully even when we have paddings, 347 // since we simulate it to be opaque. 348 int paddedBottom = getHeight() - getPaddingBottom(); 349 int paddedRight = getWidth() - getPaddingRight(); 350 if (getPaddingTop() != 0) { 351 canvas.drawRect(0, 0, getWidth(), getPaddingTop(), mTransparentSrcPaint); 352 } 353 if (getPaddingBottom() != 0) { 354 canvas.drawRect(0, paddedBottom, getWidth(), getHeight(), mTransparentSrcPaint); 355 } 356 if (getPaddingLeft() != 0) { 357 canvas.drawRect(0, getPaddingTop(), getPaddingLeft(), paddedBottom, 358 mTransparentSrcPaint); 359 } 360 if (getPaddingRight() != 0) { 361 canvas.drawRect(paddedRight, getPaddingTop(), getWidth(), paddedBottom, 362 mTransparentSrcPaint); 363 } 364 } 365 if (DEBUG) { 366 Paint pt = new Paint(); 367 pt.setColor(0x80FFFF00); 368 pt.setStrokeWidth(12.0f); 369 pt.setStyle(Paint.Style.STROKE); 370 canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), pt); 371 } 372 } 373 374 public void cancelExpandHelper() { 375 if (mStackScrollLayout != null) { 376 mStackScrollLayout.cancelExpandHelper(); 377 } 378 } 379 380 public void cancelCurrentTouch() { 381 if (mTouchActive) { 382 final long now = SystemClock.uptimeMillis(); 383 MotionEvent event = MotionEvent.obtain(now, now, 384 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); 385 event.setSource(InputDevice.SOURCE_TOUCHSCREEN); 386 dispatchTouchEvent(event); 387 event.recycle(); 388 mTouchCancelled = true; 389 } 390 } 391 392 public class LayoutParams extends FrameLayout.LayoutParams { 393 394 public boolean ignoreRightInset; 395 396 public LayoutParams(int width, int height) { 397 super(width, height); 398 } 399 400 public LayoutParams(Context c, AttributeSet attrs) { 401 super(c, attrs); 402 403 TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout); 404 ignoreRightInset = a.getBoolean( 405 R.styleable.StatusBarWindowView_Layout_ignoreRightInset, false); 406 a.recycle(); 407 } 408 } 409 410 @Override 411 public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback, 412 int type) { 413 if (type == ActionMode.TYPE_FLOATING) { 414 return startActionMode(originalView, callback, type); 415 } 416 return super.startActionModeForChild(originalView, callback, type); 417 } 418 419 private ActionMode createFloatingActionMode( 420 View originatingView, ActionMode.Callback2 callback) { 421 if (mFloatingActionMode != null) { 422 mFloatingActionMode.finish(); 423 } 424 cleanupFloatingActionModeViews(); 425 mFloatingToolbar = new FloatingToolbar(mFakeWindow); 426 final FloatingActionMode mode = 427 new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar); 428 mFloatingActionModeOriginatingView = originatingView; 429 mFloatingToolbarPreDrawListener = 430 new ViewTreeObserver.OnPreDrawListener() { 431 @Override 432 public boolean onPreDraw() { 433 mode.updateViewLocationInWindow(); 434 return true; 435 } 436 }; 437 return mode; 438 } 439 440 private void setHandledFloatingActionMode(ActionMode mode) { 441 mFloatingActionMode = mode; 442 mFloatingActionMode.invalidate(); // Will show the floating toolbar if necessary. 443 mFloatingActionModeOriginatingView.getViewTreeObserver() 444 .addOnPreDrawListener(mFloatingToolbarPreDrawListener); 445 } 446 447 private void cleanupFloatingActionModeViews() { 448 if (mFloatingToolbar != null) { 449 mFloatingToolbar.dismiss(); 450 mFloatingToolbar = null; 451 } 452 if (mFloatingActionModeOriginatingView != null) { 453 if (mFloatingToolbarPreDrawListener != null) { 454 mFloatingActionModeOriginatingView.getViewTreeObserver() 455 .removeOnPreDrawListener(mFloatingToolbarPreDrawListener); 456 mFloatingToolbarPreDrawListener = null; 457 } 458 mFloatingActionModeOriginatingView = null; 459 } 460 } 461 462 private ActionMode startActionMode( 463 View originatingView, ActionMode.Callback callback, int type) { 464 ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback); 465 ActionMode mode = createFloatingActionMode(originatingView, wrappedCallback); 466 if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) { 467 setHandledFloatingActionMode(mode); 468 } else { 469 mode = null; 470 } 471 return mode; 472 } 473 474 private class ActionModeCallback2Wrapper extends ActionMode.Callback2 { 475 private final ActionMode.Callback mWrapped; 476 477 public ActionModeCallback2Wrapper(ActionMode.Callback wrapped) { 478 mWrapped = wrapped; 479 } 480 481 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 482 return mWrapped.onCreateActionMode(mode, menu); 483 } 484 485 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 486 requestFitSystemWindows(); 487 return mWrapped.onPrepareActionMode(mode, menu); 488 } 489 490 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 491 return mWrapped.onActionItemClicked(mode, item); 492 } 493 494 public void onDestroyActionMode(ActionMode mode) { 495 mWrapped.onDestroyActionMode(mode); 496 if (mode == mFloatingActionMode) { 497 cleanupFloatingActionModeViews(); 498 mFloatingActionMode = null; 499 } 500 requestFitSystemWindows(); 501 } 502 503 @Override 504 public void onGetContentRect(ActionMode mode, View view, Rect outRect) { 505 if (mWrapped instanceof ActionMode.Callback2) { 506 ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect); 507 } else { 508 super.onGetContentRect(mode, view, outRect); 509 } 510 } 511 } 512 513 /** 514 * Minimal window to satisfy FloatingToolbar. 515 */ 516 private Window mFakeWindow = new Window(mContext) { 517 @Override 518 public void takeSurface(SurfaceHolder.Callback2 callback) { 519 } 520 521 @Override 522 public void takeInputQueue(InputQueue.Callback callback) { 523 } 524 525 @Override 526 public boolean isFloating() { 527 return false; 528 } 529 530 @Override 531 public void alwaysReadCloseOnTouchAttr() { 532 } 533 534 @Override 535 public void setContentView(@LayoutRes int layoutResID) { 536 } 537 538 @Override 539 public void setContentView(View view) { 540 } 541 542 @Override 543 public void setContentView(View view, ViewGroup.LayoutParams params) { 544 } 545 546 @Override 547 public void addContentView(View view, ViewGroup.LayoutParams params) { 548 } 549 550 @Override 551 public void clearContentView() { 552 } 553 554 @Override 555 public View getCurrentFocus() { 556 return null; 557 } 558 559 @Override 560 public LayoutInflater getLayoutInflater() { 561 return null; 562 } 563 564 @Override 565 public void setTitle(CharSequence title) { 566 } 567 568 @Override 569 public void setTitleColor(@ColorInt int textColor) { 570 } 571 572 @Override 573 public void openPanel(int featureId, KeyEvent event) { 574 } 575 576 @Override 577 public void closePanel(int featureId) { 578 } 579 580 @Override 581 public void togglePanel(int featureId, KeyEvent event) { 582 } 583 584 @Override 585 public void invalidatePanelMenu(int featureId) { 586 } 587 588 @Override 589 public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) { 590 return false; 591 } 592 593 @Override 594 public boolean performPanelIdentifierAction(int featureId, int id, int flags) { 595 return false; 596 } 597 598 @Override 599 public void closeAllPanels() { 600 } 601 602 @Override 603 public boolean performContextMenuIdentifierAction(int id, int flags) { 604 return false; 605 } 606 607 @Override 608 public void onConfigurationChanged(Configuration newConfig) { 609 } 610 611 @Override 612 public void setBackgroundDrawable(Drawable drawable) { 613 } 614 615 @Override 616 public void setFeatureDrawableResource(int featureId, @DrawableRes int resId) { 617 } 618 619 @Override 620 public void setFeatureDrawableUri(int featureId, Uri uri) { 621 } 622 623 @Override 624 public void setFeatureDrawable(int featureId, Drawable drawable) { 625 } 626 627 @Override 628 public void setFeatureDrawableAlpha(int featureId, int alpha) { 629 } 630 631 @Override 632 public void setFeatureInt(int featureId, int value) { 633 } 634 635 @Override 636 public void takeKeyEvents(boolean get) { 637 } 638 639 @Override 640 public boolean superDispatchKeyEvent(KeyEvent event) { 641 return false; 642 } 643 644 @Override 645 public boolean superDispatchKeyShortcutEvent(KeyEvent event) { 646 return false; 647 } 648 649 @Override 650 public boolean superDispatchTouchEvent(MotionEvent event) { 651 return false; 652 } 653 654 @Override 655 public boolean superDispatchTrackballEvent(MotionEvent event) { 656 return false; 657 } 658 659 @Override 660 public boolean superDispatchGenericMotionEvent(MotionEvent event) { 661 return false; 662 } 663 664 @Override 665 public View getDecorView() { 666 return StatusBarWindowView.this; 667 } 668 669 @Override 670 public View peekDecorView() { 671 return null; 672 } 673 674 @Override 675 public Bundle saveHierarchyState() { 676 return null; 677 } 678 679 @Override 680 public void restoreHierarchyState(Bundle savedInstanceState) { 681 } 682 683 @Override 684 protected void onActive() { 685 } 686 687 @Override 688 public void setChildDrawable(int featureId, Drawable drawable) { 689 } 690 691 @Override 692 public void setChildInt(int featureId, int value) { 693 } 694 695 @Override 696 public boolean isShortcutKey(int keyCode, KeyEvent event) { 697 return false; 698 } 699 700 @Override 701 public void setVolumeControlStream(int streamType) { 702 } 703 704 @Override 705 public int getVolumeControlStream() { 706 return 0; 707 } 708 709 @Override 710 public int getStatusBarColor() { 711 return 0; 712 } 713 714 @Override 715 public void setStatusBarColor(@ColorInt int color) { 716 } 717 718 @Override 719 public int getNavigationBarColor() { 720 return 0; 721 } 722 723 @Override 724 public void setNavigationBarColor(@ColorInt int color) { 725 } 726 727 @Override 728 public void setDecorCaptionShade(int decorCaptionShade) { 729 } 730 731 @Override 732 public void setResizingCaptionDrawable(Drawable drawable) { 733 } 734 735 @Override 736 public void onMultiWindowModeChanged() { 737 } 738 739 @Override 740 public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) { 741 } 742 743 @Override 744 public void reportActivityRelaunched() { 745 } 746 }; 747 748 } 749 750