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