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