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