1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.statusbar.phone;
18 
19 import static android.view.WindowInsets.Type.systemBars;
20 
21 import android.annotation.ColorInt;
22 import android.annotation.DrawableRes;
23 import android.annotation.LayoutRes;
24 import android.content.Context;
25 import android.content.res.Configuration;
26 import android.content.res.TypedArray;
27 import android.graphics.Canvas;
28 import android.graphics.Insets;
29 import android.graphics.Paint;
30 import android.graphics.Rect;
31 import android.graphics.drawable.Drawable;
32 import android.net.Uri;
33 import android.os.Bundle;
34 import android.util.AttributeSet;
35 import android.view.ActionMode;
36 import android.view.DisplayCutout;
37 import android.view.InputQueue;
38 import android.view.KeyEvent;
39 import android.view.LayoutInflater;
40 import android.view.Menu;
41 import android.view.MenuItem;
42 import android.view.MotionEvent;
43 import android.view.SurfaceHolder;
44 import android.view.View;
45 import android.view.ViewGroup;
46 import android.view.ViewTreeObserver;
47 import android.view.Window;
48 import android.view.WindowInsets;
49 import android.view.WindowInsetsController;
50 import android.widget.FrameLayout;
51 
52 import com.android.internal.view.FloatingActionMode;
53 import com.android.internal.widget.FloatingToolbar;
54 import com.android.systemui.R;
55 
56 /**
57  * Combined keyguard and notification panel view. Also holding backdrop and scrims.
58  */
59 public class NotificationShadeWindowView extends FrameLayout {
60     public static final String TAG = "NotificationShadeWindowView";
61     public static final boolean DEBUG = StatusBar.DEBUG;
62 
63     private int mRightInset = 0;
64     private int mLeftInset = 0;
65 
66     // Implements the floating action mode for TextView's Cut/Copy/Past menu. Normally provided by
67     // DecorView, but since this is a special window we have to roll our own.
68     private View mFloatingActionModeOriginatingView;
69     private ActionMode mFloatingActionMode;
70     private FloatingToolbar mFloatingToolbar;
71     private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
72 
73     private InteractionEventHandler mInteractionEventHandler;
74 
NotificationShadeWindowView(Context context, AttributeSet attrs)75     public NotificationShadeWindowView(Context context, AttributeSet attrs) {
76         super(context, attrs);
77         setMotionEventSplittingEnabled(false);
78     }
79 
getNotificationPanelView()80     public NotificationPanelView getNotificationPanelView() {
81         return findViewById(R.id.notification_panel);
82     }
83 
84     @Override
onApplyWindowInsets(WindowInsets windowInsets)85     public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) {
86         final Insets insets = windowInsets.getInsetsIgnoringVisibility(systemBars());
87         if (getFitsSystemWindows()) {
88             boolean paddingChanged = insets.top != getPaddingTop()
89                     || insets.bottom != getPaddingBottom();
90 
91             // Drop top inset, and pass through bottom inset.
92             if (paddingChanged) {
93                 setPadding(0, 0, 0, 0);
94             }
95         } else {
96             boolean changed = getPaddingLeft() != 0
97                     || getPaddingRight() != 0
98                     || getPaddingTop() != 0
99                     || getPaddingBottom() != 0;
100             if (changed) {
101                 setPadding(0, 0, 0, 0);
102             }
103         }
104 
105         mLeftInset = 0;
106         mRightInset = 0;
107         DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
108         if (displayCutout != null) {
109             mLeftInset = displayCutout.getSafeInsetLeft();
110             mRightInset = displayCutout.getSafeInsetRight();
111         }
112         mLeftInset = Math.max(insets.left, mLeftInset);
113         mRightInset = Math.max(insets.right, mRightInset);
114         applyMargins();
115         return windowInsets;
116     }
117 
applyMargins()118     private void applyMargins() {
119         final int count = getChildCount();
120         for (int i = 0; i < count; i++) {
121             View child = getChildAt(i);
122             if (child.getLayoutParams() instanceof LayoutParams) {
123                 LayoutParams lp = (LayoutParams) child.getLayoutParams();
124                 if (!lp.ignoreRightInset
125                         && (lp.rightMargin != mRightInset || lp.leftMargin != mLeftInset)) {
126                     lp.rightMargin = mRightInset;
127                     lp.leftMargin = mLeftInset;
128                     child.requestLayout();
129                 }
130             }
131         }
132     }
133 
134     @Override
generateLayoutParams(AttributeSet attrs)135     public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
136         return new LayoutParams(getContext(), attrs);
137     }
138 
139     @Override
generateDefaultLayoutParams()140     protected FrameLayout.LayoutParams generateDefaultLayoutParams() {
141         return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
142     }
143 
144     @Override
onAttachedToWindow()145     protected void onAttachedToWindow() {
146         super.onAttachedToWindow();
147         setWillNotDraw(!DEBUG);
148     }
149 
150     @Override
dispatchKeyEvent(KeyEvent event)151     public boolean dispatchKeyEvent(KeyEvent event) {
152         if (mInteractionEventHandler.interceptMediaKey(event)) {
153             return true;
154         }
155 
156         if (super.dispatchKeyEvent(event)) {
157             return true;
158         }
159 
160         return mInteractionEventHandler.dispatchKeyEvent(event);
161     }
162 
163     @Override
dispatchKeyEventPreIme(KeyEvent event)164     public boolean dispatchKeyEventPreIme(KeyEvent event) {
165         return mInteractionEventHandler.dispatchKeyEventPreIme(event);
166     }
167 
setInteractionEventHandler(InteractionEventHandler listener)168     protected void setInteractionEventHandler(InteractionEventHandler listener) {
169         mInteractionEventHandler = listener;
170     }
171 
172     @Override
dispatchTouchEvent(MotionEvent ev)173     public boolean dispatchTouchEvent(MotionEvent ev) {
174         Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev);
175 
176         result = result != null ? result : super.dispatchTouchEvent(ev);
177 
178         mInteractionEventHandler.dispatchTouchEventComplete();
179 
180         return result;
181     }
182 
183     @Override
onInterceptTouchEvent(MotionEvent ev)184     public boolean onInterceptTouchEvent(MotionEvent ev) {
185         boolean intercept = mInteractionEventHandler.shouldInterceptTouchEvent(ev);
186         if (!intercept) {
187             intercept = super.onInterceptTouchEvent(ev);
188         }
189         if (intercept) {
190             mInteractionEventHandler.didIntercept(ev);
191         }
192 
193         return intercept;
194     }
195 
196     @Override
onTouchEvent(MotionEvent ev)197     public boolean onTouchEvent(MotionEvent ev) {
198         boolean handled = mInteractionEventHandler.handleTouchEvent(ev);
199 
200         if (!handled) {
201             handled = super.onTouchEvent(ev);
202         }
203 
204         if (!handled) {
205             mInteractionEventHandler.didNotHandleTouchEvent(ev);
206         }
207 
208         return handled;
209     }
210 
211     @Override
onDraw(Canvas canvas)212     public void onDraw(Canvas canvas) {
213         super.onDraw(canvas);
214         if (DEBUG) {
215             Paint pt = new Paint();
216             pt.setColor(0x80FFFF00);
217             pt.setStrokeWidth(12.0f);
218             pt.setStyle(Paint.Style.STROKE);
219             canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), pt);
220         }
221     }
222 
223     class LayoutParams extends FrameLayout.LayoutParams {
224 
225         public boolean ignoreRightInset;
226 
LayoutParams(int width, int height)227         LayoutParams(int width, int height) {
228             super(width, height);
229         }
230 
LayoutParams(Context c, AttributeSet attrs)231         LayoutParams(Context c, AttributeSet attrs) {
232             super(c, attrs);
233 
234             TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout);
235             ignoreRightInset = a.getBoolean(
236                     R.styleable.StatusBarWindowView_Layout_ignoreRightInset, false);
237             a.recycle();
238         }
239     }
240 
241     @Override
startActionModeForChild(View originalView, ActionMode.Callback callback, int type)242     public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback,
243             int type) {
244         if (type == ActionMode.TYPE_FLOATING) {
245             return startActionMode(originalView, callback, type);
246         }
247         return super.startActionModeForChild(originalView, callback, type);
248     }
249 
createFloatingActionMode( View originatingView, ActionMode.Callback2 callback)250     private ActionMode createFloatingActionMode(
251             View originatingView, ActionMode.Callback2 callback) {
252         if (mFloatingActionMode != null) {
253             mFloatingActionMode.finish();
254         }
255         cleanupFloatingActionModeViews();
256         mFloatingToolbar = new FloatingToolbar(mFakeWindow);
257         final FloatingActionMode mode =
258                 new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar);
259         mFloatingActionModeOriginatingView = originatingView;
260         mFloatingToolbarPreDrawListener =
261                 new ViewTreeObserver.OnPreDrawListener() {
262                     @Override
263                     public boolean onPreDraw() {
264                         mode.updateViewLocationInWindow();
265                         return true;
266                     }
267                 };
268         return mode;
269     }
270 
setHandledFloatingActionMode(ActionMode mode)271     private void setHandledFloatingActionMode(ActionMode mode) {
272         mFloatingActionMode = mode;
273         mFloatingActionMode.invalidate();  // Will show the floating toolbar if necessary.
274         mFloatingActionModeOriginatingView.getViewTreeObserver()
275                 .addOnPreDrawListener(mFloatingToolbarPreDrawListener);
276     }
277 
cleanupFloatingActionModeViews()278     private void cleanupFloatingActionModeViews() {
279         if (mFloatingToolbar != null) {
280             mFloatingToolbar.dismiss();
281             mFloatingToolbar = null;
282         }
283         if (mFloatingActionModeOriginatingView != null) {
284             if (mFloatingToolbarPreDrawListener != null) {
285                 mFloatingActionModeOriginatingView.getViewTreeObserver()
286                         .removeOnPreDrawListener(mFloatingToolbarPreDrawListener);
287                 mFloatingToolbarPreDrawListener = null;
288             }
289             mFloatingActionModeOriginatingView = null;
290         }
291     }
292 
startActionMode( View originatingView, ActionMode.Callback callback, int type)293     private ActionMode startActionMode(
294             View originatingView, ActionMode.Callback callback, int type) {
295         ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
296         ActionMode mode = createFloatingActionMode(originatingView, wrappedCallback);
297         if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
298             setHandledFloatingActionMode(mode);
299         } else {
300             mode = null;
301         }
302         return mode;
303     }
304 
305     private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
306         private final ActionMode.Callback mWrapped;
307 
ActionModeCallback2Wrapper(ActionMode.Callback wrapped)308         ActionModeCallback2Wrapper(ActionMode.Callback wrapped) {
309             mWrapped = wrapped;
310         }
311 
onCreateActionMode(ActionMode mode, Menu menu)312         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
313             return mWrapped.onCreateActionMode(mode, menu);
314         }
315 
onPrepareActionMode(ActionMode mode, Menu menu)316         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
317             requestFitSystemWindows();
318             return mWrapped.onPrepareActionMode(mode, menu);
319         }
320 
onActionItemClicked(ActionMode mode, MenuItem item)321         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
322             return mWrapped.onActionItemClicked(mode, item);
323         }
324 
onDestroyActionMode(ActionMode mode)325         public void onDestroyActionMode(ActionMode mode) {
326             mWrapped.onDestroyActionMode(mode);
327             if (mode == mFloatingActionMode) {
328                 cleanupFloatingActionModeViews();
329                 mFloatingActionMode = null;
330             }
331             requestFitSystemWindows();
332         }
333 
334         @Override
onGetContentRect(ActionMode mode, View view, Rect outRect)335         public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
336             if (mWrapped instanceof ActionMode.Callback2) {
337                 ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect);
338             } else {
339                 super.onGetContentRect(mode, view, outRect);
340             }
341         }
342     }
343 
344     interface InteractionEventHandler {
345         /**
346          * Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer
347          * to the super method.
348          */
handleDispatchTouchEvent(MotionEvent ev)349         Boolean handleDispatchTouchEvent(MotionEvent ev);
350 
351         /**
352          * Called after all dispatching is done.
353          */
354 
dispatchTouchEventComplete()355         void dispatchTouchEventComplete();
356 
357         /**
358          * Returns if the view should intercept the touch event.
359          *
360          * The touch event may still be interecepted if
361          * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)} decides to do so.
362          */
shouldInterceptTouchEvent(MotionEvent ev)363         boolean shouldInterceptTouchEvent(MotionEvent ev);
364 
365         /**
366          * Called when the view decides to intercept the touch event.
367          */
didIntercept(MotionEvent ev)368         void didIntercept(MotionEvent ev);
369 
handleTouchEvent(MotionEvent ev)370         boolean handleTouchEvent(MotionEvent ev);
371 
didNotHandleTouchEvent(MotionEvent ev)372         void didNotHandleTouchEvent(MotionEvent ev);
373 
interceptMediaKey(KeyEvent event)374         boolean interceptMediaKey(KeyEvent event);
375 
dispatchKeyEvent(KeyEvent event)376         boolean dispatchKeyEvent(KeyEvent event);
377 
dispatchKeyEventPreIme(KeyEvent event)378         boolean dispatchKeyEventPreIme(KeyEvent event);
379     }
380 
381     /**
382      * Minimal window to satisfy FloatingToolbar.
383      */
384     private Window mFakeWindow = new Window(mContext) {
385         @Override
386         public void takeSurface(SurfaceHolder.Callback2 callback) {
387         }
388 
389         @Override
390         public void takeInputQueue(InputQueue.Callback callback) {
391         }
392 
393         @Override
394         public boolean isFloating() {
395             return false;
396         }
397 
398         @Override
399         public void alwaysReadCloseOnTouchAttr() {
400         }
401 
402         @Override
403         public void setContentView(@LayoutRes int layoutResID) {
404         }
405 
406         @Override
407         public void setContentView(View view) {
408         }
409 
410         @Override
411         public void setContentView(View view, ViewGroup.LayoutParams params) {
412         }
413 
414         @Override
415         public void addContentView(View view, ViewGroup.LayoutParams params) {
416         }
417 
418         @Override
419         public void clearContentView() {
420         }
421 
422         @Override
423         public View getCurrentFocus() {
424             return null;
425         }
426 
427         @Override
428         public LayoutInflater getLayoutInflater() {
429             return null;
430         }
431 
432         @Override
433         public void setTitle(CharSequence title) {
434         }
435 
436         @Override
437         public void setTitleColor(@ColorInt int textColor) {
438         }
439 
440         @Override
441         public void openPanel(int featureId, KeyEvent event) {
442         }
443 
444         @Override
445         public void closePanel(int featureId) {
446         }
447 
448         @Override
449         public void togglePanel(int featureId, KeyEvent event) {
450         }
451 
452         @Override
453         public void invalidatePanelMenu(int featureId) {
454         }
455 
456         @Override
457         public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) {
458             return false;
459         }
460 
461         @Override
462         public boolean performPanelIdentifierAction(int featureId, int id, int flags) {
463             return false;
464         }
465 
466         @Override
467         public void closeAllPanels() {
468         }
469 
470         @Override
471         public boolean performContextMenuIdentifierAction(int id, int flags) {
472             return false;
473         }
474 
475         @Override
476         public void onConfigurationChanged(Configuration newConfig) {
477         }
478 
479         @Override
480         public void setBackgroundDrawable(Drawable drawable) {
481         }
482 
483         @Override
484         public void setFeatureDrawableResource(int featureId, @DrawableRes int resId) {
485         }
486 
487         @Override
488         public void setFeatureDrawableUri(int featureId, Uri uri) {
489         }
490 
491         @Override
492         public void setFeatureDrawable(int featureId, Drawable drawable) {
493         }
494 
495         @Override
496         public void setFeatureDrawableAlpha(int featureId, int alpha) {
497         }
498 
499         @Override
500         public void setFeatureInt(int featureId, int value) {
501         }
502 
503         @Override
504         public void takeKeyEvents(boolean get) {
505         }
506 
507         @Override
508         public boolean superDispatchKeyEvent(KeyEvent event) {
509             return false;
510         }
511 
512         @Override
513         public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
514             return false;
515         }
516 
517         @Override
518         public boolean superDispatchTouchEvent(MotionEvent event) {
519             return false;
520         }
521 
522         @Override
523         public boolean superDispatchTrackballEvent(MotionEvent event) {
524             return false;
525         }
526 
527         @Override
528         public boolean superDispatchGenericMotionEvent(MotionEvent event) {
529             return false;
530         }
531 
532         @Override
533         public View getDecorView() {
534             return NotificationShadeWindowView.this;
535         }
536 
537         @Override
538         public View peekDecorView() {
539             return null;
540         }
541 
542         @Override
543         public Bundle saveHierarchyState() {
544             return null;
545         }
546 
547         @Override
548         public void restoreHierarchyState(Bundle savedInstanceState) {
549         }
550 
551         @Override
552         protected void onActive() {
553         }
554 
555         @Override
556         public void setChildDrawable(int featureId, Drawable drawable) {
557         }
558 
559         @Override
560         public void setChildInt(int featureId, int value) {
561         }
562 
563         @Override
564         public boolean isShortcutKey(int keyCode, KeyEvent event) {
565             return false;
566         }
567 
568         @Override
569         public void setVolumeControlStream(int streamType) {
570         }
571 
572         @Override
573         public int getVolumeControlStream() {
574             return 0;
575         }
576 
577         @Override
578         public int getStatusBarColor() {
579             return 0;
580         }
581 
582         @Override
583         public void setStatusBarColor(@ColorInt int color) {
584         }
585 
586         @Override
587         public int getNavigationBarColor() {
588             return 0;
589         }
590 
591         @Override
592         public void setNavigationBarColor(@ColorInt int color) {
593         }
594 
595         @Override
596         public void setDecorCaptionShade(int decorCaptionShade) {
597         }
598 
599         @Override
600         public void setResizingCaptionDrawable(Drawable drawable) {
601         }
602 
603         @Override
604         public void onMultiWindowModeChanged() {
605         }
606 
607         @Override
608         public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
609         }
610 
611         @Override
612         public void reportActivityRelaunched() {
613         }
614 
615         @Override
616         public WindowInsetsController getInsetsController() {
617             return null;
618         }
619     };
620 
621 }
622 
623