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 
setInteractionEventHandler(InteractionEventHandler listener)163     protected void setInteractionEventHandler(InteractionEventHandler listener) {
164         mInteractionEventHandler = listener;
165     }
166 
167     @Override
dispatchTouchEvent(MotionEvent ev)168     public boolean dispatchTouchEvent(MotionEvent ev) {
169         Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev);
170 
171         return result != null ? result : super.dispatchTouchEvent(ev);
172     }
173 
174     @Override
onInterceptTouchEvent(MotionEvent ev)175     public boolean onInterceptTouchEvent(MotionEvent ev) {
176         boolean intercept = mInteractionEventHandler.shouldInterceptTouchEvent(ev);
177         if (!intercept) {
178             intercept = super.onInterceptTouchEvent(ev);
179         }
180         if (intercept) {
181             mInteractionEventHandler.didIntercept(ev);
182         }
183 
184         return intercept;
185     }
186 
187     @Override
onTouchEvent(MotionEvent ev)188     public boolean onTouchEvent(MotionEvent ev) {
189         boolean handled = mInteractionEventHandler.handleTouchEvent(ev);
190 
191         if (!handled) {
192             handled = super.onTouchEvent(ev);
193         }
194 
195         if (!handled) {
196             mInteractionEventHandler.didNotHandleTouchEvent(ev);
197         }
198 
199         return handled;
200     }
201 
202     @Override
onDraw(Canvas canvas)203     public void onDraw(Canvas canvas) {
204         super.onDraw(canvas);
205         if (DEBUG) {
206             Paint pt = new Paint();
207             pt.setColor(0x80FFFF00);
208             pt.setStrokeWidth(12.0f);
209             pt.setStyle(Paint.Style.STROKE);
210             canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), pt);
211         }
212     }
213 
214     class LayoutParams extends FrameLayout.LayoutParams {
215 
216         public boolean ignoreRightInset;
217 
LayoutParams(int width, int height)218         LayoutParams(int width, int height) {
219             super(width, height);
220         }
221 
LayoutParams(Context c, AttributeSet attrs)222         LayoutParams(Context c, AttributeSet attrs) {
223             super(c, attrs);
224 
225             TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout);
226             ignoreRightInset = a.getBoolean(
227                     R.styleable.StatusBarWindowView_Layout_ignoreRightInset, false);
228             a.recycle();
229         }
230     }
231 
232     @Override
startActionModeForChild(View originalView, ActionMode.Callback callback, int type)233     public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback,
234             int type) {
235         if (type == ActionMode.TYPE_FLOATING) {
236             return startActionMode(originalView, callback, type);
237         }
238         return super.startActionModeForChild(originalView, callback, type);
239     }
240 
createFloatingActionMode( View originatingView, ActionMode.Callback2 callback)241     private ActionMode createFloatingActionMode(
242             View originatingView, ActionMode.Callback2 callback) {
243         if (mFloatingActionMode != null) {
244             mFloatingActionMode.finish();
245         }
246         cleanupFloatingActionModeViews();
247         mFloatingToolbar = new FloatingToolbar(mFakeWindow);
248         final FloatingActionMode mode =
249                 new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar);
250         mFloatingActionModeOriginatingView = originatingView;
251         mFloatingToolbarPreDrawListener =
252                 new ViewTreeObserver.OnPreDrawListener() {
253                     @Override
254                     public boolean onPreDraw() {
255                         mode.updateViewLocationInWindow();
256                         return true;
257                     }
258                 };
259         return mode;
260     }
261 
setHandledFloatingActionMode(ActionMode mode)262     private void setHandledFloatingActionMode(ActionMode mode) {
263         mFloatingActionMode = mode;
264         mFloatingActionMode.invalidate();  // Will show the floating toolbar if necessary.
265         mFloatingActionModeOriginatingView.getViewTreeObserver()
266                 .addOnPreDrawListener(mFloatingToolbarPreDrawListener);
267     }
268 
cleanupFloatingActionModeViews()269     private void cleanupFloatingActionModeViews() {
270         if (mFloatingToolbar != null) {
271             mFloatingToolbar.dismiss();
272             mFloatingToolbar = null;
273         }
274         if (mFloatingActionModeOriginatingView != null) {
275             if (mFloatingToolbarPreDrawListener != null) {
276                 mFloatingActionModeOriginatingView.getViewTreeObserver()
277                         .removeOnPreDrawListener(mFloatingToolbarPreDrawListener);
278                 mFloatingToolbarPreDrawListener = null;
279             }
280             mFloatingActionModeOriginatingView = null;
281         }
282     }
283 
startActionMode( View originatingView, ActionMode.Callback callback, int type)284     private ActionMode startActionMode(
285             View originatingView, ActionMode.Callback callback, int type) {
286         ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
287         ActionMode mode = createFloatingActionMode(originatingView, wrappedCallback);
288         if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
289             setHandledFloatingActionMode(mode);
290         } else {
291             mode = null;
292         }
293         return mode;
294     }
295 
296     private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
297         private final ActionMode.Callback mWrapped;
298 
ActionModeCallback2Wrapper(ActionMode.Callback wrapped)299         ActionModeCallback2Wrapper(ActionMode.Callback wrapped) {
300             mWrapped = wrapped;
301         }
302 
onCreateActionMode(ActionMode mode, Menu menu)303         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
304             return mWrapped.onCreateActionMode(mode, menu);
305         }
306 
onPrepareActionMode(ActionMode mode, Menu menu)307         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
308             requestFitSystemWindows();
309             return mWrapped.onPrepareActionMode(mode, menu);
310         }
311 
onActionItemClicked(ActionMode mode, MenuItem item)312         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
313             return mWrapped.onActionItemClicked(mode, item);
314         }
315 
onDestroyActionMode(ActionMode mode)316         public void onDestroyActionMode(ActionMode mode) {
317             mWrapped.onDestroyActionMode(mode);
318             if (mode == mFloatingActionMode) {
319                 cleanupFloatingActionModeViews();
320                 mFloatingActionMode = null;
321             }
322             requestFitSystemWindows();
323         }
324 
325         @Override
onGetContentRect(ActionMode mode, View view, Rect outRect)326         public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
327             if (mWrapped instanceof ActionMode.Callback2) {
328                 ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect);
329             } else {
330                 super.onGetContentRect(mode, view, outRect);
331             }
332         }
333     }
334 
335     interface InteractionEventHandler {
336         /**
337          * Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer
338          * to the super method.
339          */
handleDispatchTouchEvent(MotionEvent ev)340         Boolean handleDispatchTouchEvent(MotionEvent ev);
341 
342         /**
343          * Returns if the view should intercept the touch event.
344          *
345          * The touch event may still be interecepted if
346          * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)} decides to do so.
347          */
shouldInterceptTouchEvent(MotionEvent ev)348         boolean shouldInterceptTouchEvent(MotionEvent ev);
349 
350         /**
351          * Called when the view decides to intercept the touch event.
352          */
didIntercept(MotionEvent ev)353         void didIntercept(MotionEvent ev);
354 
handleTouchEvent(MotionEvent ev)355         boolean handleTouchEvent(MotionEvent ev);
356 
didNotHandleTouchEvent(MotionEvent ev)357         void didNotHandleTouchEvent(MotionEvent ev);
358 
interceptMediaKey(KeyEvent event)359         boolean interceptMediaKey(KeyEvent event);
360 
dispatchKeyEvent(KeyEvent event)361         boolean dispatchKeyEvent(KeyEvent event);
362     }
363 
364     /**
365      * Minimal window to satisfy FloatingToolbar.
366      */
367     private Window mFakeWindow = new Window(mContext) {
368         @Override
369         public void takeSurface(SurfaceHolder.Callback2 callback) {
370         }
371 
372         @Override
373         public void takeInputQueue(InputQueue.Callback callback) {
374         }
375 
376         @Override
377         public boolean isFloating() {
378             return false;
379         }
380 
381         @Override
382         public void alwaysReadCloseOnTouchAttr() {
383         }
384 
385         @Override
386         public void setContentView(@LayoutRes int layoutResID) {
387         }
388 
389         @Override
390         public void setContentView(View view) {
391         }
392 
393         @Override
394         public void setContentView(View view, ViewGroup.LayoutParams params) {
395         }
396 
397         @Override
398         public void addContentView(View view, ViewGroup.LayoutParams params) {
399         }
400 
401         @Override
402         public void clearContentView() {
403         }
404 
405         @Override
406         public View getCurrentFocus() {
407             return null;
408         }
409 
410         @Override
411         public LayoutInflater getLayoutInflater() {
412             return null;
413         }
414 
415         @Override
416         public void setTitle(CharSequence title) {
417         }
418 
419         @Override
420         public void setTitleColor(@ColorInt int textColor) {
421         }
422 
423         @Override
424         public void openPanel(int featureId, KeyEvent event) {
425         }
426 
427         @Override
428         public void closePanel(int featureId) {
429         }
430 
431         @Override
432         public void togglePanel(int featureId, KeyEvent event) {
433         }
434 
435         @Override
436         public void invalidatePanelMenu(int featureId) {
437         }
438 
439         @Override
440         public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) {
441             return false;
442         }
443 
444         @Override
445         public boolean performPanelIdentifierAction(int featureId, int id, int flags) {
446             return false;
447         }
448 
449         @Override
450         public void closeAllPanels() {
451         }
452 
453         @Override
454         public boolean performContextMenuIdentifierAction(int id, int flags) {
455             return false;
456         }
457 
458         @Override
459         public void onConfigurationChanged(Configuration newConfig) {
460         }
461 
462         @Override
463         public void setBackgroundDrawable(Drawable drawable) {
464         }
465 
466         @Override
467         public void setFeatureDrawableResource(int featureId, @DrawableRes int resId) {
468         }
469 
470         @Override
471         public void setFeatureDrawableUri(int featureId, Uri uri) {
472         }
473 
474         @Override
475         public void setFeatureDrawable(int featureId, Drawable drawable) {
476         }
477 
478         @Override
479         public void setFeatureDrawableAlpha(int featureId, int alpha) {
480         }
481 
482         @Override
483         public void setFeatureInt(int featureId, int value) {
484         }
485 
486         @Override
487         public void takeKeyEvents(boolean get) {
488         }
489 
490         @Override
491         public boolean superDispatchKeyEvent(KeyEvent event) {
492             return false;
493         }
494 
495         @Override
496         public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
497             return false;
498         }
499 
500         @Override
501         public boolean superDispatchTouchEvent(MotionEvent event) {
502             return false;
503         }
504 
505         @Override
506         public boolean superDispatchTrackballEvent(MotionEvent event) {
507             return false;
508         }
509 
510         @Override
511         public boolean superDispatchGenericMotionEvent(MotionEvent event) {
512             return false;
513         }
514 
515         @Override
516         public View getDecorView() {
517             return NotificationShadeWindowView.this;
518         }
519 
520         @Override
521         public View peekDecorView() {
522             return null;
523         }
524 
525         @Override
526         public Bundle saveHierarchyState() {
527             return null;
528         }
529 
530         @Override
531         public void restoreHierarchyState(Bundle savedInstanceState) {
532         }
533 
534         @Override
535         protected void onActive() {
536         }
537 
538         @Override
539         public void setChildDrawable(int featureId, Drawable drawable) {
540         }
541 
542         @Override
543         public void setChildInt(int featureId, int value) {
544         }
545 
546         @Override
547         public boolean isShortcutKey(int keyCode, KeyEvent event) {
548             return false;
549         }
550 
551         @Override
552         public void setVolumeControlStream(int streamType) {
553         }
554 
555         @Override
556         public int getVolumeControlStream() {
557             return 0;
558         }
559 
560         @Override
561         public int getStatusBarColor() {
562             return 0;
563         }
564 
565         @Override
566         public void setStatusBarColor(@ColorInt int color) {
567         }
568 
569         @Override
570         public int getNavigationBarColor() {
571             return 0;
572         }
573 
574         @Override
575         public void setNavigationBarColor(@ColorInt int color) {
576         }
577 
578         @Override
579         public void setDecorCaptionShade(int decorCaptionShade) {
580         }
581 
582         @Override
583         public void setResizingCaptionDrawable(Drawable drawable) {
584         }
585 
586         @Override
587         public void onMultiWindowModeChanged() {
588         }
589 
590         @Override
591         public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
592         }
593 
594         @Override
595         public void reportActivityRelaunched() {
596         }
597 
598         @Override
599         public WindowInsetsController getInsetsController() {
600             return null;
601         }
602     };
603 
604 }
605 
606