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 
21 import static com.android.systemui.Flags.enableViewCaptureTracing;
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.content.Context;
28 import android.content.res.Configuration;
29 import android.graphics.Canvas;
30 import android.graphics.Paint;
31 import android.graphics.Rect;
32 import android.graphics.drawable.Drawable;
33 import android.media.permission.SafeCloseable;
34 import android.net.Uri;
35 import android.os.Bundle;
36 import android.os.Trace;
37 import android.util.AttributeSet;
38 import android.view.ActionMode;
39 import android.view.InputQueue;
40 import android.view.KeyEvent;
41 import android.view.LayoutInflater;
42 import android.view.Menu;
43 import android.view.MenuItem;
44 import android.view.MotionEvent;
45 import android.view.SurfaceHolder;
46 import android.view.View;
47 import android.view.ViewGroup;
48 import android.view.ViewTreeObserver;
49 import android.view.Window;
50 import android.view.WindowInsetsController;
51 
52 import com.android.app.viewcapture.ViewCaptureFactory;
53 import com.android.internal.view.FloatingActionMode;
54 import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
55 import com.android.systemui.scene.ui.view.WindowRootView;
56 
57 /**
58  * Combined keyguard and notification panel view. Also holding backdrop and scrims. This view can
59  * serve as the root view of the main SysUI window, but because other views can also serve that
60  * purpose, users of this class cannot assume it is the root.
61  */
62 public class NotificationShadeWindowView extends WindowRootView {
63     public static final String TAG = "NotificationShadeWindowView";
64 
65     // Implements the floating action mode for TextView's Cut/Copy/Past menu. Normally provided by
66     // DecorView, but since this is a special window we have to roll our own.
67     private View mFloatingActionModeOriginatingView;
68     private ActionMode mFloatingActionMode;
69     private FloatingToolbar mFloatingToolbar;
70     private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
71 
72     private InteractionEventHandler mInteractionEventHandler;
73 
74     private SafeCloseable mViewCaptureCloseable;
75 
NotificationShadeWindowView(Context context, AttributeSet attrs)76     public NotificationShadeWindowView(Context context, AttributeSet attrs) {
77         super(context, attrs);
78         setMotionEventSplittingEnabled(false);
79     }
80 
81     @Override
onAttachedToWindow()82     protected void onAttachedToWindow() {
83         super.onAttachedToWindow();
84         setWillNotDraw(!DEBUG);
85         if (enableViewCaptureTracing()) {
86             mViewCaptureCloseable = ViewCaptureFactory.getInstance(getContext())
87                 .startCapture(getRootView(), ".NotificationShadeWindowView");
88         }
89     }
90 
91     @Override
onDetachedFromWindow()92     protected void onDetachedFromWindow() {
93         super.onDetachedFromWindow();
94         if (mViewCaptureCloseable != null) {
95             mViewCaptureCloseable.close();
96         }
97     }
98 
99     @Override
dispatchKeyEvent(KeyEvent event)100     public boolean dispatchKeyEvent(KeyEvent event) {
101         mInteractionEventHandler.collectKeyEvent(event);
102 
103         if (mInteractionEventHandler.interceptMediaKey(event)) {
104             return true;
105         }
106 
107         if (super.dispatchKeyEvent(event)) {
108             return true;
109         }
110 
111         return mInteractionEventHandler.dispatchKeyEvent(event);
112     }
113 
114     @Override
dispatchKeyEventPreIme(KeyEvent event)115     public boolean dispatchKeyEventPreIme(KeyEvent event) {
116         return mInteractionEventHandler.dispatchKeyEventPreIme(event);
117     }
118 
setInteractionEventHandler(InteractionEventHandler listener)119     protected void setInteractionEventHandler(InteractionEventHandler listener) {
120         mInteractionEventHandler = listener;
121     }
122 
123     @Override
dispatchTouchEvent(MotionEvent ev)124     public boolean dispatchTouchEvent(MotionEvent ev) {
125         Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev);
126 
127         result = result != null ? result : super.dispatchTouchEvent(ev);
128 
129         TouchLogger.logDispatchTouch(TAG, ev, result);
130 
131         mInteractionEventHandler.dispatchTouchEventComplete();
132 
133         return result;
134     }
135 
136     @Override
onInterceptTouchEvent(MotionEvent ev)137     public boolean onInterceptTouchEvent(MotionEvent ev) {
138         boolean intercept = mInteractionEventHandler.shouldInterceptTouchEvent(ev);
139         if (!intercept) {
140             intercept = super.onInterceptTouchEvent(ev);
141         }
142         if (intercept) {
143             mInteractionEventHandler.didIntercept(ev);
144         }
145 
146         return intercept;
147     }
148 
149     @Override
onTouchEvent(MotionEvent ev)150     public boolean onTouchEvent(MotionEvent ev) {
151         boolean handled = mInteractionEventHandler.handleTouchEvent(ev);
152 
153         if (!handled) {
154             handled = super.onTouchEvent(ev);
155         }
156 
157         if (!handled) {
158             mInteractionEventHandler.didNotHandleTouchEvent(ev);
159         }
160 
161         return handled;
162     }
163 
164     @Override
onDraw(Canvas canvas)165     public void onDraw(Canvas canvas) {
166         super.onDraw(canvas);
167         if (DEBUG) {
168             Paint pt = new Paint();
169             pt.setColor(0x80FFFF00);
170             pt.setStrokeWidth(12.0f);
171             pt.setStyle(Paint.Style.STROKE);
172             canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), pt);
173         }
174     }
175 
176     @Override
startActionModeForChild(View originalView, ActionMode.Callback callback, int type)177     public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback,
178             int type) {
179         if (type == ActionMode.TYPE_FLOATING) {
180             return startActionMode(originalView, callback);
181         }
182         return super.startActionModeForChild(originalView, callback, type);
183     }
184 
createFloatingActionMode( View originatingView, ActionMode.Callback2 callback)185     private ActionMode createFloatingActionMode(
186             View originatingView, ActionMode.Callback2 callback) {
187         if (mFloatingActionMode != null) {
188             mFloatingActionMode.finish();
189         }
190         cleanupFloatingActionModeViews();
191         mFloatingToolbar = new FloatingToolbar(mFakeWindow);
192         final FloatingActionMode mode =
193                 new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar);
194         mFloatingActionModeOriginatingView = originatingView;
195         mFloatingToolbarPreDrawListener = () -> {
196             mode.updateViewLocationInWindow();
197             return true;
198         };
199         return mode;
200     }
201 
setHandledFloatingActionMode(ActionMode mode)202     private void setHandledFloatingActionMode(ActionMode mode) {
203         mFloatingActionMode = mode;
204         mFloatingActionMode.invalidate();  // Will show the floating toolbar if necessary.
205         mFloatingActionModeOriginatingView.getViewTreeObserver()
206                 .addOnPreDrawListener(mFloatingToolbarPreDrawListener);
207     }
208 
cleanupFloatingActionModeViews()209     private void cleanupFloatingActionModeViews() {
210         if (mFloatingToolbar != null) {
211             mFloatingToolbar.dismiss();
212             mFloatingToolbar = null;
213         }
214         if (mFloatingActionModeOriginatingView != null) {
215             if (mFloatingToolbarPreDrawListener != null) {
216                 mFloatingActionModeOriginatingView.getViewTreeObserver()
217                         .removeOnPreDrawListener(mFloatingToolbarPreDrawListener);
218                 mFloatingToolbarPreDrawListener = null;
219             }
220             mFloatingActionModeOriginatingView = null;
221         }
222     }
223 
startActionMode( View originatingView, ActionMode.Callback callback)224     private ActionMode startActionMode(
225             View originatingView, ActionMode.Callback callback) {
226         ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
227         ActionMode mode = createFloatingActionMode(originatingView, wrappedCallback);
228         if (wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
229             setHandledFloatingActionMode(mode);
230         } else {
231             mode = null;
232         }
233         return mode;
234     }
235 
236     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)237     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
238         Trace.beginSection("NotificationShadeWindowView#onMeasure");
239         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
240         Trace.endSection();
241     }
242 
243     @Override
requestLayout()244     public void requestLayout() {
245         Trace.instant(TRACE_TAG_APP, "NotificationShadeWindowView#requestLayout");
246         super.requestLayout();
247     }
248 
249     private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
250         private final ActionMode.Callback mWrapped;
251 
ActionModeCallback2Wrapper(ActionMode.Callback wrapped)252         ActionModeCallback2Wrapper(ActionMode.Callback wrapped) {
253             mWrapped = wrapped;
254         }
255 
onCreateActionMode(ActionMode mode, Menu menu)256         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
257             return mWrapped.onCreateActionMode(mode, menu);
258         }
259 
onPrepareActionMode(ActionMode mode, Menu menu)260         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
261             requestFitSystemWindows();
262             return mWrapped.onPrepareActionMode(mode, menu);
263         }
264 
onActionItemClicked(ActionMode mode, MenuItem item)265         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
266             return mWrapped.onActionItemClicked(mode, item);
267         }
268 
onDestroyActionMode(ActionMode mode)269         public void onDestroyActionMode(ActionMode mode) {
270             mWrapped.onDestroyActionMode(mode);
271             if (mode == mFloatingActionMode) {
272                 cleanupFloatingActionModeViews();
273                 mFloatingActionMode = null;
274             }
275             requestFitSystemWindows();
276         }
277 
278         @Override
onGetContentRect(ActionMode mode, View view, Rect outRect)279         public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
280             if (mWrapped instanceof ActionMode.Callback2) {
281                 ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect);
282             } else {
283                 super.onGetContentRect(mode, view, outRect);
284             }
285         }
286     }
287 
288     interface InteractionEventHandler {
289         /**
290          * Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer
291          * to the super method.
292          */
handleDispatchTouchEvent(MotionEvent ev)293         Boolean handleDispatchTouchEvent(MotionEvent ev);
294 
295         /**
296          * Called after all dispatching is done.
297          */
298 
dispatchTouchEventComplete()299         void dispatchTouchEventComplete();
300 
301         /**
302          * Returns if the view should intercept the touch event.
303          *
304          * The touch event may still be interecepted if
305          * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)} decides to do so.
306          */
shouldInterceptTouchEvent(MotionEvent ev)307         boolean shouldInterceptTouchEvent(MotionEvent ev);
308 
309         /**
310          * Called when the view decides to intercept the touch event.
311          */
didIntercept(MotionEvent ev)312         void didIntercept(MotionEvent ev);
313 
handleTouchEvent(MotionEvent ev)314         boolean handleTouchEvent(MotionEvent ev);
315 
didNotHandleTouchEvent(MotionEvent ev)316         void didNotHandleTouchEvent(MotionEvent ev);
317 
interceptMediaKey(KeyEvent event)318         boolean interceptMediaKey(KeyEvent event);
319 
dispatchKeyEvent(KeyEvent event)320         boolean dispatchKeyEvent(KeyEvent event);
321 
dispatchKeyEventPreIme(KeyEvent event)322         boolean dispatchKeyEventPreIme(KeyEvent event);
323 
324         /**
325          * Collects the KeyEvent without intercepting it
326          */
collectKeyEvent(KeyEvent event)327         void collectKeyEvent(KeyEvent event);
328     }
329 
330     /**
331      * Minimal window to satisfy FloatingToolbar.
332      */
333     private final Window mFakeWindow = new Window(mContext) {
334         @Override
335         public void takeSurface(SurfaceHolder.Callback2 callback) {
336         }
337 
338         @Override
339         public void takeInputQueue(InputQueue.Callback callback) {
340         }
341 
342         @Override
343         public boolean isFloating() {
344             return false;
345         }
346 
347         @Override
348         public void alwaysReadCloseOnTouchAttr() {
349         }
350 
351         @Override
352         public void setContentView(@LayoutRes int layoutResID) {
353         }
354 
355         @Override
356         public void setContentView(View view) {
357         }
358 
359         @Override
360         public void setContentView(View view, ViewGroup.LayoutParams params) {
361         }
362 
363         @Override
364         public void addContentView(View view, ViewGroup.LayoutParams params) {
365         }
366 
367         @Override
368         public void clearContentView() {
369         }
370 
371         @Override
372         public View getCurrentFocus() {
373             return null;
374         }
375 
376         @Override
377         public LayoutInflater getLayoutInflater() {
378             return null;
379         }
380 
381         @Override
382         public void setTitle(CharSequence title) {
383         }
384 
385         @Override
386         public void setTitleColor(@ColorInt int textColor) {
387         }
388 
389         @Override
390         public void openPanel(int featureId, KeyEvent event) {
391         }
392 
393         @Override
394         public void closePanel(int featureId) {
395         }
396 
397         @Override
398         public void togglePanel(int featureId, KeyEvent event) {
399         }
400 
401         @Override
402         public void invalidatePanelMenu(int featureId) {
403         }
404 
405         @Override
406         public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) {
407             return false;
408         }
409 
410         @Override
411         public boolean performPanelIdentifierAction(int featureId, int id, int flags) {
412             return false;
413         }
414 
415         @Override
416         public void closeAllPanels() {
417         }
418 
419         @Override
420         public boolean performContextMenuIdentifierAction(int id, int flags) {
421             return false;
422         }
423 
424         @Override
425         public void onConfigurationChanged(Configuration newConfig) {
426         }
427 
428         @Override
429         public void setBackgroundDrawable(Drawable drawable) {
430         }
431 
432         @Override
433         public void setFeatureDrawableResource(int featureId, @DrawableRes int resId) {
434         }
435 
436         @Override
437         public void setFeatureDrawableUri(int featureId, Uri uri) {
438         }
439 
440         @Override
441         public void setFeatureDrawable(int featureId, Drawable drawable) {
442         }
443 
444         @Override
445         public void setFeatureDrawableAlpha(int featureId, int alpha) {
446         }
447 
448         @Override
449         public void setFeatureInt(int featureId, int value) {
450         }
451 
452         @Override
453         public void takeKeyEvents(boolean get) {
454         }
455 
456         @Override
457         public boolean superDispatchKeyEvent(KeyEvent event) {
458             return false;
459         }
460 
461         @Override
462         public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
463             return false;
464         }
465 
466         @Override
467         public boolean superDispatchTouchEvent(MotionEvent event) {
468             return false;
469         }
470 
471         @Override
472         public boolean superDispatchTrackballEvent(MotionEvent event) {
473             return false;
474         }
475 
476         @Override
477         public boolean superDispatchGenericMotionEvent(MotionEvent event) {
478             return false;
479         }
480 
481         @Override
482         public View getDecorView() {
483             return NotificationShadeWindowView.this;
484         }
485 
486         @Override
487         public View peekDecorView() {
488             return null;
489         }
490 
491         @Override
492         public Bundle saveHierarchyState() {
493             return null;
494         }
495 
496         @Override
497         public void restoreHierarchyState(Bundle savedInstanceState) {
498         }
499 
500         @Override
501         protected void onActive() {
502         }
503 
504         @Override
505         public void setChildDrawable(int featureId, Drawable drawable) {
506         }
507 
508         @Override
509         public void setChildInt(int featureId, int value) {
510         }
511 
512         @Override
513         public boolean isShortcutKey(int keyCode, KeyEvent event) {
514             return false;
515         }
516 
517         @Override
518         public void setVolumeControlStream(int streamType) {
519         }
520 
521         @Override
522         public int getVolumeControlStream() {
523             return 0;
524         }
525 
526         @Override
527         public int getStatusBarColor() {
528             return 0;
529         }
530 
531         @Override
532         public void setStatusBarColor(@ColorInt int color) {
533         }
534 
535         @Override
536         public int getNavigationBarColor() {
537             return 0;
538         }
539 
540         @Override
541         public void setNavigationBarColor(@ColorInt int color) {
542         }
543 
544         @Override
545         public void setDecorCaptionShade(int decorCaptionShade) {
546         }
547 
548         @Override
549         public void setResizingCaptionDrawable(Drawable drawable) {
550         }
551 
552         @Override
553         public void onMultiWindowModeChanged() {
554         }
555 
556         @Override
557         public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
558         }
559 
560         @Override
561         public WindowInsetsController getInsetsController() {
562             return null;
563         }
564     };
565 
566 }
567 
568