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.hardware.display.AmbientDisplayConfiguration; 33 import android.media.AudioManager; 34 import android.media.session.MediaSessionLegacyHelper; 35 import android.net.Uri; 36 import android.os.Bundle; 37 import android.os.SystemClock; 38 import android.os.UserHandle; 39 import android.provider.Settings; 40 import android.util.AttributeSet; 41 import android.view.ActionMode; 42 import android.view.DisplayCutout; 43 import android.view.GestureDetector; 44 import android.view.InputDevice; 45 import android.view.InputQueue; 46 import android.view.KeyEvent; 47 import android.view.LayoutInflater; 48 import android.view.Menu; 49 import android.view.MenuItem; 50 import android.view.MotionEvent; 51 import android.view.SurfaceHolder; 52 import android.view.View; 53 import android.view.ViewGroup; 54 import android.view.ViewTreeObserver; 55 import android.view.Window; 56 import android.view.WindowInsetsController; 57 import android.widget.FrameLayout; 58 59 import com.android.internal.annotations.VisibleForTesting; 60 import com.android.internal.view.FloatingActionMode; 61 import com.android.internal.widget.FloatingToolbar; 62 import com.android.systemui.Dependency; 63 import com.android.systemui.ExpandHelper; 64 import com.android.systemui.R; 65 import com.android.systemui.plugins.FalsingManager; 66 import com.android.systemui.plugins.statusbar.StatusBarStateController; 67 import com.android.systemui.statusbar.DragDownHelper; 68 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; 69 import com.android.systemui.statusbar.phone.ScrimController.ScrimVisibility; 70 import com.android.systemui.tuner.TunerService; 71 72 import java.io.FileDescriptor; 73 import java.io.PrintWriter; 74 75 /** 76 * Combined status bar and notification panel view. Also holding backdrop and scrims. 77 */ 78 public class StatusBarWindowView extends FrameLayout { 79 public static final String TAG = "StatusBarWindowView"; 80 public static final boolean DEBUG = StatusBar.DEBUG; 81 82 private final GestureDetector mGestureDetector; 83 private final StatusBarStateController mStatusBarStateController; 84 private boolean mDoubleTapEnabled; 85 private boolean mSingleTapEnabled; 86 private DragDownHelper mDragDownHelper; 87 private NotificationStackScrollLayout mStackScrollLayout; 88 private NotificationPanelView mNotificationPanel; 89 private View mBrightnessMirror; 90 private LockIcon mLockIcon; 91 private PhoneStatusBarView mStatusBarView; 92 private PhoneStatusBarTransitions mBarTransitions; 93 94 private int mRightInset = 0; 95 private int mLeftInset = 0; 96 97 private StatusBar mService; 98 private final Paint mTransparentSrcPaint = new Paint(); 99 private FalsingManager mFalsingManager; 100 101 // Implements the floating action mode for TextView's Cut/Copy/Past menu. Normally provided by 102 // DecorView, but since this is a special window we have to roll our own. 103 private View mFloatingActionModeOriginatingView; 104 private ActionMode mFloatingActionMode; 105 private FloatingToolbar mFloatingToolbar; 106 private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener; 107 private boolean mTouchCancelled; 108 private boolean mTouchActive; 109 private boolean mExpandAnimationRunning; 110 private boolean mExpandAnimationPending; 111 private boolean mSuppressingWakeUpGesture; 112 113 private final GestureDetector.SimpleOnGestureListener mGestureListener = 114 new GestureDetector.SimpleOnGestureListener() { 115 @Override 116 public boolean onSingleTapConfirmed(MotionEvent e) { 117 if (mSingleTapEnabled && !mSuppressingWakeUpGesture) { 118 mService.wakeUpIfDozing(SystemClock.uptimeMillis(), StatusBarWindowView.this, 119 "SINGLE_TAP"); 120 return true; 121 } 122 return false; 123 } 124 125 @Override 126 public boolean onDoubleTap(MotionEvent e) { 127 if (mDoubleTapEnabled || mSingleTapEnabled) { 128 mService.wakeUpIfDozing(SystemClock.uptimeMillis(), StatusBarWindowView.this, 129 "DOUBLE_TAP"); 130 return true; 131 } 132 return false; 133 } 134 }; 135 private final TunerService.Tunable mTunable = (key, newValue) -> { 136 AmbientDisplayConfiguration configuration = new AmbientDisplayConfiguration(mContext); 137 switch (key) { 138 case Settings.Secure.DOZE_DOUBLE_TAP_GESTURE: 139 mDoubleTapEnabled = configuration.doubleTapGestureEnabled(UserHandle.USER_CURRENT); 140 break; 141 case Settings.Secure.DOZE_TAP_SCREEN_GESTURE: 142 mSingleTapEnabled = configuration.tapGestureEnabled(UserHandle.USER_CURRENT); 143 } 144 }; 145 146 /** 147 * If set to true, the current gesture started below the notch and we need to dispatch touch 148 * events manually as it's outside of the regular view bounds. 149 */ 150 private boolean mExpandingBelowNotch; 151 private KeyguardBypassController mBypassController; 152 StatusBarWindowView(Context context, AttributeSet attrs)153 public StatusBarWindowView(Context context, AttributeSet attrs) { 154 super(context, attrs); 155 setMotionEventSplittingEnabled(false); 156 mTransparentSrcPaint.setColor(0); 157 mTransparentSrcPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); 158 mFalsingManager = Dependency.get(FalsingManager.class); // TODO: inject into a controller. 159 mGestureDetector = new GestureDetector(context, mGestureListener); 160 mStatusBarStateController = Dependency.get(StatusBarStateController.class); 161 Dependency.get(TunerService.class).addTunable(mTunable, 162 Settings.Secure.DOZE_DOUBLE_TAP_GESTURE, 163 Settings.Secure.DOZE_TAP_SCREEN_GESTURE); 164 } 165 166 @Override fitSystemWindows(Rect insets)167 protected boolean fitSystemWindows(Rect insets) { 168 if (getFitsSystemWindows()) { 169 boolean paddingChanged = insets.top != getPaddingTop() 170 || insets.bottom != getPaddingBottom(); 171 172 int rightCutout = 0; 173 int leftCutout = 0; 174 DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout(); 175 if (displayCutout != null) { 176 leftCutout = displayCutout.getSafeInsetLeft(); 177 rightCutout = displayCutout.getSafeInsetRight(); 178 } 179 180 int targetLeft = Math.max(insets.left, leftCutout); 181 int targetRight = Math.max(insets.right, rightCutout); 182 183 // Super-special right inset handling, because scrims, backdrop and status bar 184 // container need to ignore it. 185 if (targetRight != mRightInset || targetLeft != mLeftInset) { 186 mRightInset = targetRight; 187 mLeftInset = targetLeft; 188 applyMargins(); 189 } 190 // Drop top inset, and pass through bottom inset. 191 if (paddingChanged) { 192 setPadding(0, 0, 0, 0); 193 } 194 insets.left = 0; 195 insets.top = 0; 196 insets.right = 0; 197 } else { 198 if (mRightInset != 0 || mLeftInset != 0) { 199 mRightInset = 0; 200 mLeftInset = 0; 201 applyMargins(); 202 } 203 boolean changed = getPaddingLeft() != 0 204 || getPaddingRight() != 0 205 || getPaddingTop() != 0 206 || getPaddingBottom() != 0; 207 if (changed) { 208 setPadding(0, 0, 0, 0); 209 } 210 insets.top = 0; 211 } 212 return false; 213 } 214 applyMargins()215 private void applyMargins() { 216 final int N = getChildCount(); 217 for (int i = 0; i < N; i++) { 218 View child = getChildAt(i); 219 if (child.getLayoutParams() instanceof LayoutParams) { 220 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 221 if (!lp.ignoreRightInset 222 && (lp.rightMargin != mRightInset || lp.leftMargin != mLeftInset)) { 223 lp.rightMargin = mRightInset; 224 lp.leftMargin = mLeftInset; 225 child.requestLayout(); 226 } 227 } 228 } 229 } 230 231 @VisibleForTesting getStackScrollLayout()232 protected NotificationStackScrollLayout getStackScrollLayout() { 233 return mStackScrollLayout; 234 } 235 236 @Override generateLayoutParams(AttributeSet attrs)237 public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) { 238 return new LayoutParams(getContext(), attrs); 239 } 240 241 @Override generateDefaultLayoutParams()242 protected FrameLayout.LayoutParams generateDefaultLayoutParams() { 243 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 244 } 245 246 @Override onFinishInflate()247 protected void onFinishInflate() { 248 super.onFinishInflate(); 249 mStackScrollLayout = findViewById(R.id.notification_stack_scroller); 250 mNotificationPanel = findViewById(R.id.notification_panel); 251 mBrightnessMirror = findViewById(R.id.brightness_mirror); 252 mLockIcon = findViewById(R.id.lock_icon); 253 } 254 255 @Override onViewAdded(View child)256 public void onViewAdded(View child) { 257 super.onViewAdded(child); 258 if (child.getId() == R.id.brightness_mirror) { 259 mBrightnessMirror = child; 260 } 261 } 262 263 /** 264 * Propagate {@link StatusBar} pulsing state. 265 */ setPulsing(boolean pulsing)266 public void setPulsing(boolean pulsing) { 267 if (mLockIcon != null) { 268 mLockIcon.setPulsing(pulsing); 269 } 270 } 271 272 /** 273 * Called when the biometric authentication mode changes. 274 * @param wakeAndUnlock If the type is {@link BiometricUnlockController#isWakeAndUnlock()} 275 * @param isUnlock If the type is {@link BiometricUnlockController#isBiometricUnlock()} () 276 */ onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock)277 public void onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock) { 278 if (mLockIcon != null) { 279 mLockIcon.onBiometricAuthModeChanged(wakeAndUnlock, isUnlock); 280 } 281 } 282 setStatusBarView(PhoneStatusBarView statusBarView)283 public void setStatusBarView(PhoneStatusBarView statusBarView) { 284 mStatusBarView = statusBarView; 285 mBarTransitions = new PhoneStatusBarTransitions(statusBarView, 286 findViewById(R.id.status_bar_container)); 287 } 288 getBarTransitions()289 public PhoneStatusBarTransitions getBarTransitions() { 290 return mBarTransitions; 291 } 292 setService(StatusBar service)293 public void setService(StatusBar service) { 294 mService = service; 295 NotificationStackScrollLayout stackScrollLayout = getStackScrollLayout(); 296 ExpandHelper.Callback expandHelperCallback = stackScrollLayout.getExpandHelperCallback(); 297 DragDownHelper.DragDownCallback dragDownCallback = stackScrollLayout.getDragDownCallback(); 298 setDragDownHelper(new DragDownHelper(getContext(), this, expandHelperCallback, 299 dragDownCallback, mFalsingManager)); 300 } 301 302 @VisibleForTesting setDragDownHelper(DragDownHelper dragDownHelper)303 void setDragDownHelper(DragDownHelper dragDownHelper) { 304 mDragDownHelper = dragDownHelper; 305 } 306 307 @Override onAttachedToWindow()308 protected void onAttachedToWindow () { 309 super.onAttachedToWindow(); 310 setWillNotDraw(!DEBUG); 311 } 312 313 @Override dispatchKeyEvent(KeyEvent event)314 public boolean dispatchKeyEvent(KeyEvent event) { 315 if (mService.interceptMediaKey(event)) { 316 return true; 317 } 318 if (super.dispatchKeyEvent(event)) { 319 return true; 320 } 321 boolean down = event.getAction() == KeyEvent.ACTION_DOWN; 322 switch (event.getKeyCode()) { 323 case KeyEvent.KEYCODE_BACK: 324 if (!down) { 325 mService.onBackPressed(); 326 } 327 return true; 328 case KeyEvent.KEYCODE_MENU: 329 if (!down) { 330 return mService.onMenuPressed(); 331 } 332 case KeyEvent.KEYCODE_SPACE: 333 if (!down) { 334 return mService.onSpacePressed(); 335 } 336 break; 337 case KeyEvent.KEYCODE_VOLUME_DOWN: 338 case KeyEvent.KEYCODE_VOLUME_UP: 339 if (mService.isDozing()) { 340 MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent( 341 event, AudioManager.USE_DEFAULT_STREAM_TYPE, true); 342 return true; 343 } 344 break; 345 } 346 return false; 347 } 348 setTouchActive(boolean touchActive)349 public void setTouchActive(boolean touchActive) { 350 mTouchActive = touchActive; 351 } 352 suppressWakeUpGesture(boolean suppress)353 void suppressWakeUpGesture(boolean suppress) { 354 mSuppressingWakeUpGesture = suppress; 355 } 356 357 @Override dispatchTouchEvent(MotionEvent ev)358 public boolean dispatchTouchEvent(MotionEvent ev) { 359 boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN; 360 boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP; 361 boolean isCancel = ev.getActionMasked() == MotionEvent.ACTION_CANCEL; 362 363 // Reset manual touch dispatch state here but make sure the UP/CANCEL event still gets 364 // delivered. 365 boolean expandingBelowNotch = mExpandingBelowNotch; 366 if (isUp || isCancel) { 367 mExpandingBelowNotch = false; 368 } 369 370 if (!isCancel && mService.shouldIgnoreTouch()) { 371 return false; 372 } 373 if (isDown && mNotificationPanel.isFullyCollapsed()) { 374 mNotificationPanel.startExpandLatencyTracking(); 375 } 376 if (isDown) { 377 setTouchActive(true); 378 mTouchCancelled = false; 379 } else if (ev.getActionMasked() == MotionEvent.ACTION_UP 380 || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) { 381 setTouchActive(false); 382 } 383 if (mTouchCancelled || mExpandAnimationRunning || mExpandAnimationPending) { 384 return false; 385 } 386 mFalsingManager.onTouchEvent(ev, getWidth(), getHeight()); 387 mGestureDetector.onTouchEvent(ev); 388 if (mBrightnessMirror != null && mBrightnessMirror.getVisibility() == VISIBLE) { 389 // Disallow new pointers while the brightness mirror is visible. This is so that you 390 // can't touch anything other than the brightness slider while the mirror is showing 391 // and the rest of the panel is transparent. 392 if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { 393 return false; 394 } 395 } 396 if (isDown) { 397 getStackScrollLayout().closeControlsIfOutsideTouch(ev); 398 } 399 if (mService.isDozing()) { 400 mService.mDozeScrimController.extendPulse(); 401 } 402 403 // In case we start outside of the view bounds (below the status bar), we need to dispatch 404 // the touch manually as the view system can't accomodate for touches outside of the 405 // regular view bounds. 406 if (isDown && ev.getY() >= mBottom) { 407 mExpandingBelowNotch = true; 408 expandingBelowNotch = true; 409 } 410 if (expandingBelowNotch) { 411 return mStatusBarView.dispatchTouchEvent(ev); 412 } 413 414 return super.dispatchTouchEvent(ev); 415 } 416 417 @Override onInterceptTouchEvent(MotionEvent ev)418 public boolean onInterceptTouchEvent(MotionEvent ev) { 419 NotificationStackScrollLayout stackScrollLayout = getStackScrollLayout(); 420 if (mService.isDozing() && !mService.isPulsing()) { 421 // Capture all touch events in always-on. 422 return true; 423 } 424 boolean intercept = false; 425 if (mNotificationPanel.isFullyExpanded() 426 && mDragDownHelper.isDragDownEnabled() 427 && !mService.isBouncerShowing() 428 && !mService.isDozing()) { 429 intercept = mDragDownHelper.onInterceptTouchEvent(ev); 430 } 431 if (!intercept) { 432 super.onInterceptTouchEvent(ev); 433 } 434 if (intercept) { 435 MotionEvent cancellation = MotionEvent.obtain(ev); 436 cancellation.setAction(MotionEvent.ACTION_CANCEL); 437 stackScrollLayout.onInterceptTouchEvent(cancellation); 438 mNotificationPanel.onInterceptTouchEvent(cancellation); 439 cancellation.recycle(); 440 } 441 return intercept; 442 } 443 444 @Override onTouchEvent(MotionEvent ev)445 public boolean onTouchEvent(MotionEvent ev) { 446 boolean handled = false; 447 if (mService.isDozing()) { 448 handled = !mService.isPulsing(); 449 } 450 if ((mDragDownHelper.isDragDownEnabled() && !handled) || mDragDownHelper.isDraggingDown()) { 451 // we still want to finish our drag down gesture when locking the screen 452 handled = mDragDownHelper.onTouchEvent(ev); 453 } 454 if (!handled) { 455 handled = super.onTouchEvent(ev); 456 } 457 final int action = ev.getAction(); 458 if (!handled && (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)) { 459 mService.setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); 460 } 461 return handled; 462 } 463 464 @Override onDraw(Canvas canvas)465 public void onDraw(Canvas canvas) { 466 super.onDraw(canvas); 467 if (DEBUG) { 468 Paint pt = new Paint(); 469 pt.setColor(0x80FFFF00); 470 pt.setStrokeWidth(12.0f); 471 pt.setStyle(Paint.Style.STROKE); 472 canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), pt); 473 } 474 } 475 cancelExpandHelper()476 public void cancelExpandHelper() { 477 NotificationStackScrollLayout stackScrollLayout = getStackScrollLayout(); 478 if (stackScrollLayout != null) { 479 stackScrollLayout.cancelExpandHelper(); 480 } 481 } 482 cancelCurrentTouch()483 public void cancelCurrentTouch() { 484 if (mTouchActive) { 485 final long now = SystemClock.uptimeMillis(); 486 MotionEvent event = MotionEvent.obtain(now, now, 487 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); 488 event.setSource(InputDevice.SOURCE_TOUCHSCREEN); 489 dispatchTouchEvent(event); 490 event.recycle(); 491 mTouchCancelled = true; 492 } 493 } 494 setExpandAnimationRunning(boolean expandAnimationRunning)495 public void setExpandAnimationRunning(boolean expandAnimationRunning) { 496 mExpandAnimationRunning = expandAnimationRunning; 497 } 498 setExpandAnimationPending(boolean pending)499 public void setExpandAnimationPending(boolean pending) { 500 mExpandAnimationPending = pending; 501 } 502 dump(FileDescriptor fd, PrintWriter pw, String[] args)503 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 504 pw.print(" mExpandAnimationPending="); pw.println(mExpandAnimationPending); 505 pw.print(" mExpandAnimationRunning="); pw.println(mExpandAnimationRunning); 506 pw.print(" mTouchCancelled="); pw.println(mTouchCancelled); 507 pw.print(" mTouchActive="); pw.println(mTouchActive); 508 } 509 510 /** 511 * Called whenever the scrims become opaque, transparent or semi-transparent. 512 */ onScrimVisibilityChanged(@crimVisibility int scrimsVisible)513 public void onScrimVisibilityChanged(@ScrimVisibility int scrimsVisible) { 514 if (mLockIcon != null) { 515 mLockIcon.onScrimVisibilityChanged(scrimsVisible); 516 } 517 } 518 519 /** 520 * When we're launching an affordance, like double pressing power to open camera. 521 */ onShowingLaunchAffordanceChanged(boolean showing)522 public void onShowingLaunchAffordanceChanged(boolean showing) { 523 if (mLockIcon != null) { 524 mLockIcon.onShowingLaunchAffordanceChanged(showing); 525 } 526 } 527 setBypassController(KeyguardBypassController bypassController)528 public void setBypassController(KeyguardBypassController bypassController) { 529 mBypassController = bypassController; 530 } 531 setBouncerShowingScrimmed(boolean bouncerShowing)532 public void setBouncerShowingScrimmed(boolean bouncerShowing) { 533 if (mLockIcon != null) { 534 mLockIcon.setBouncerShowingScrimmed(bouncerShowing); 535 } 536 } 537 538 public class LayoutParams extends FrameLayout.LayoutParams { 539 540 public boolean ignoreRightInset; 541 LayoutParams(int width, int height)542 public LayoutParams(int width, int height) { 543 super(width, height); 544 } 545 LayoutParams(Context c, AttributeSet attrs)546 public LayoutParams(Context c, AttributeSet attrs) { 547 super(c, attrs); 548 549 TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout); 550 ignoreRightInset = a.getBoolean( 551 R.styleable.StatusBarWindowView_Layout_ignoreRightInset, false); 552 a.recycle(); 553 } 554 } 555 556 @Override startActionModeForChild(View originalView, ActionMode.Callback callback, int type)557 public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback, 558 int type) { 559 if (type == ActionMode.TYPE_FLOATING) { 560 return startActionMode(originalView, callback, type); 561 } 562 return super.startActionModeForChild(originalView, callback, type); 563 } 564 createFloatingActionMode( View originatingView, ActionMode.Callback2 callback)565 private ActionMode createFloatingActionMode( 566 View originatingView, ActionMode.Callback2 callback) { 567 if (mFloatingActionMode != null) { 568 mFloatingActionMode.finish(); 569 } 570 cleanupFloatingActionModeViews(); 571 mFloatingToolbar = new FloatingToolbar(mFakeWindow); 572 final FloatingActionMode mode = 573 new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar); 574 mFloatingActionModeOriginatingView = originatingView; 575 mFloatingToolbarPreDrawListener = 576 new ViewTreeObserver.OnPreDrawListener() { 577 @Override 578 public boolean onPreDraw() { 579 mode.updateViewLocationInWindow(); 580 return true; 581 } 582 }; 583 return mode; 584 } 585 setHandledFloatingActionMode(ActionMode mode)586 private void setHandledFloatingActionMode(ActionMode mode) { 587 mFloatingActionMode = mode; 588 mFloatingActionMode.invalidate(); // Will show the floating toolbar if necessary. 589 mFloatingActionModeOriginatingView.getViewTreeObserver() 590 .addOnPreDrawListener(mFloatingToolbarPreDrawListener); 591 } 592 cleanupFloatingActionModeViews()593 private void cleanupFloatingActionModeViews() { 594 if (mFloatingToolbar != null) { 595 mFloatingToolbar.dismiss(); 596 mFloatingToolbar = null; 597 } 598 if (mFloatingActionModeOriginatingView != null) { 599 if (mFloatingToolbarPreDrawListener != null) { 600 mFloatingActionModeOriginatingView.getViewTreeObserver() 601 .removeOnPreDrawListener(mFloatingToolbarPreDrawListener); 602 mFloatingToolbarPreDrawListener = null; 603 } 604 mFloatingActionModeOriginatingView = null; 605 } 606 } 607 startActionMode( View originatingView, ActionMode.Callback callback, int type)608 private ActionMode startActionMode( 609 View originatingView, ActionMode.Callback callback, int type) { 610 ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback); 611 ActionMode mode = createFloatingActionMode(originatingView, wrappedCallback); 612 if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) { 613 setHandledFloatingActionMode(mode); 614 } else { 615 mode = null; 616 } 617 return mode; 618 } 619 620 private class ActionModeCallback2Wrapper extends ActionMode.Callback2 { 621 private final ActionMode.Callback mWrapped; 622 ActionModeCallback2Wrapper(ActionMode.Callback wrapped)623 public ActionModeCallback2Wrapper(ActionMode.Callback wrapped) { 624 mWrapped = wrapped; 625 } 626 onCreateActionMode(ActionMode mode, Menu menu)627 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 628 return mWrapped.onCreateActionMode(mode, menu); 629 } 630 onPrepareActionMode(ActionMode mode, Menu menu)631 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 632 requestFitSystemWindows(); 633 return mWrapped.onPrepareActionMode(mode, menu); 634 } 635 onActionItemClicked(ActionMode mode, MenuItem item)636 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 637 return mWrapped.onActionItemClicked(mode, item); 638 } 639 onDestroyActionMode(ActionMode mode)640 public void onDestroyActionMode(ActionMode mode) { 641 mWrapped.onDestroyActionMode(mode); 642 if (mode == mFloatingActionMode) { 643 cleanupFloatingActionModeViews(); 644 mFloatingActionMode = null; 645 } 646 requestFitSystemWindows(); 647 } 648 649 @Override onGetContentRect(ActionMode mode, View view, Rect outRect)650 public void onGetContentRect(ActionMode mode, View view, Rect outRect) { 651 if (mWrapped instanceof ActionMode.Callback2) { 652 ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect); 653 } else { 654 super.onGetContentRect(mode, view, outRect); 655 } 656 } 657 } 658 659 /** 660 * Minimal window to satisfy FloatingToolbar. 661 */ 662 private Window mFakeWindow = new Window(mContext) { 663 @Override 664 public void takeSurface(SurfaceHolder.Callback2 callback) { 665 } 666 667 @Override 668 public void takeInputQueue(InputQueue.Callback callback) { 669 } 670 671 @Override 672 public boolean isFloating() { 673 return false; 674 } 675 676 @Override 677 public void alwaysReadCloseOnTouchAttr() { 678 } 679 680 @Override 681 public void setContentView(@LayoutRes int layoutResID) { 682 } 683 684 @Override 685 public void setContentView(View view) { 686 } 687 688 @Override 689 public void setContentView(View view, ViewGroup.LayoutParams params) { 690 } 691 692 @Override 693 public void addContentView(View view, ViewGroup.LayoutParams params) { 694 } 695 696 @Override 697 public void clearContentView() { 698 } 699 700 @Override 701 public View getCurrentFocus() { 702 return null; 703 } 704 705 @Override 706 public LayoutInflater getLayoutInflater() { 707 return null; 708 } 709 710 @Override 711 public void setTitle(CharSequence title) { 712 } 713 714 @Override 715 public void setTitleColor(@ColorInt int textColor) { 716 } 717 718 @Override 719 public void openPanel(int featureId, KeyEvent event) { 720 } 721 722 @Override 723 public void closePanel(int featureId) { 724 } 725 726 @Override 727 public void togglePanel(int featureId, KeyEvent event) { 728 } 729 730 @Override 731 public void invalidatePanelMenu(int featureId) { 732 } 733 734 @Override 735 public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) { 736 return false; 737 } 738 739 @Override 740 public boolean performPanelIdentifierAction(int featureId, int id, int flags) { 741 return false; 742 } 743 744 @Override 745 public void closeAllPanels() { 746 } 747 748 @Override 749 public boolean performContextMenuIdentifierAction(int id, int flags) { 750 return false; 751 } 752 753 @Override 754 public void onConfigurationChanged(Configuration newConfig) { 755 } 756 757 @Override 758 public void setBackgroundDrawable(Drawable drawable) { 759 } 760 761 @Override 762 public void setFeatureDrawableResource(int featureId, @DrawableRes int resId) { 763 } 764 765 @Override 766 public void setFeatureDrawableUri(int featureId, Uri uri) { 767 } 768 769 @Override 770 public void setFeatureDrawable(int featureId, Drawable drawable) { 771 } 772 773 @Override 774 public void setFeatureDrawableAlpha(int featureId, int alpha) { 775 } 776 777 @Override 778 public void setFeatureInt(int featureId, int value) { 779 } 780 781 @Override 782 public void takeKeyEvents(boolean get) { 783 } 784 785 @Override 786 public boolean superDispatchKeyEvent(KeyEvent event) { 787 return false; 788 } 789 790 @Override 791 public boolean superDispatchKeyShortcutEvent(KeyEvent event) { 792 return false; 793 } 794 795 @Override 796 public boolean superDispatchTouchEvent(MotionEvent event) { 797 return false; 798 } 799 800 @Override 801 public boolean superDispatchTrackballEvent(MotionEvent event) { 802 return false; 803 } 804 805 @Override 806 public boolean superDispatchGenericMotionEvent(MotionEvent event) { 807 return false; 808 } 809 810 @Override 811 public View getDecorView() { 812 return StatusBarWindowView.this; 813 } 814 815 @Override 816 public View peekDecorView() { 817 return null; 818 } 819 820 @Override 821 public Bundle saveHierarchyState() { 822 return null; 823 } 824 825 @Override 826 public void restoreHierarchyState(Bundle savedInstanceState) { 827 } 828 829 @Override 830 protected void onActive() { 831 } 832 833 @Override 834 public void setChildDrawable(int featureId, Drawable drawable) { 835 } 836 837 @Override 838 public void setChildInt(int featureId, int value) { 839 } 840 841 @Override 842 public boolean isShortcutKey(int keyCode, KeyEvent event) { 843 return false; 844 } 845 846 @Override 847 public void setVolumeControlStream(int streamType) { 848 } 849 850 @Override 851 public int getVolumeControlStream() { 852 return 0; 853 } 854 855 @Override 856 public int getStatusBarColor() { 857 return 0; 858 } 859 860 @Override 861 public void setStatusBarColor(@ColorInt int color) { 862 } 863 864 @Override 865 public int getNavigationBarColor() { 866 return 0; 867 } 868 869 @Override 870 public void setNavigationBarColor(@ColorInt int color) { 871 } 872 873 @Override 874 public void setDecorCaptionShade(int decorCaptionShade) { 875 } 876 877 @Override 878 public void setResizingCaptionDrawable(Drawable drawable) { 879 } 880 881 @Override 882 public void onMultiWindowModeChanged() { 883 } 884 885 @Override 886 public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) { 887 } 888 889 @Override 890 public void reportActivityRelaunched() { 891 } 892 893 @Override 894 public WindowInsetsController getInsetsController() { 895 return null; 896 } 897 }; 898 899 } 900 901