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.media.session.MediaSessionLegacyHelper;
33 import android.net.Uri;
34 import android.os.Bundle;
35 import android.os.IBinder;
36 import android.util.AttributeSet;
37 import android.view.ActionMode;
38 import android.view.InputQueue;
39 import android.view.KeyEvent;
40 import android.view.LayoutInflater;
41 import android.view.Menu;
42 import android.view.MenuItem;
43 import android.view.MotionEvent;
44 import android.view.SurfaceHolder;
45 import android.view.View;
46 import android.view.ViewGroup;
47 import android.view.ViewTreeObserver;
48 import android.view.Window;
49 import android.view.WindowManager;
50 import android.view.WindowManagerGlobal;
51 import android.widget.FrameLayout;
52 
53 import com.android.internal.view.FloatingActionMode;
54 import com.android.internal.widget.FloatingToolbar;
55 import com.android.systemui.R;
56 import com.android.systemui.classifier.FalsingManager;
57 import com.android.systemui.statusbar.BaseStatusBar;
58 import com.android.systemui.statusbar.DragDownHelper;
59 import com.android.systemui.statusbar.StatusBarState;
60 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
61 
62 
63 public class StatusBarWindowView extends FrameLayout {
64     public static final String TAG = "StatusBarWindowView";
65     public static final boolean DEBUG = BaseStatusBar.DEBUG;
66 
67     private DragDownHelper mDragDownHelper;
68     private NotificationStackScrollLayout mStackScrollLayout;
69     private NotificationPanelView mNotificationPanel;
70     private View mBrightnessMirror;
71 
72     private int mRightInset = 0;
73 
74     private PhoneStatusBar mService;
75     private final Paint mTransparentSrcPaint = new Paint();
76     private FalsingManager mFalsingManager;
77 
78     // Implements the floating action mode for TextView's Cut/Copy/Past menu. Normally provided by
79     // DecorView, but since this is a special window we have to roll our own.
80     private View mFloatingActionModeOriginatingView;
81     private ActionMode mFloatingActionMode;
82     private FloatingToolbar mFloatingToolbar;
83     private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
84 
StatusBarWindowView(Context context, AttributeSet attrs)85     public StatusBarWindowView(Context context, AttributeSet attrs) {
86         super(context, attrs);
87         setMotionEventSplittingEnabled(false);
88         mTransparentSrcPaint.setColor(0);
89         mTransparentSrcPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
90         mFalsingManager = FalsingManager.getInstance(context);
91     }
92 
93     @Override
fitSystemWindows(Rect insets)94     protected boolean fitSystemWindows(Rect insets) {
95         if (getFitsSystemWindows()) {
96             boolean paddingChanged = insets.left != getPaddingLeft()
97                     || insets.top != getPaddingTop()
98                     || insets.bottom != getPaddingBottom();
99 
100             // Super-special right inset handling, because scrims and backdrop need to ignore it.
101             if (insets.right != mRightInset) {
102                 mRightInset = insets.right;
103                 applyMargins();
104             }
105             // Drop top inset, apply left inset and pass through bottom inset.
106             if (paddingChanged) {
107                 setPadding(insets.left, 0, 0, 0);
108             }
109             insets.left = 0;
110             insets.top = 0;
111             insets.right = 0;
112         } else {
113             if (mRightInset != 0) {
114                 mRightInset = 0;
115                 applyMargins();
116             }
117             boolean changed = getPaddingLeft() != 0
118                     || getPaddingRight() != 0
119                     || getPaddingTop() != 0
120                     || getPaddingBottom() != 0;
121             if (changed) {
122                 setPadding(0, 0, 0, 0);
123             }
124             insets.top = 0;
125         }
126         return false;
127     }
128 
applyMargins()129     private void applyMargins() {
130         final int N = getChildCount();
131         for (int i = 0; i < N; i++) {
132             View child = getChildAt(i);
133             if (child.getLayoutParams() instanceof LayoutParams) {
134                 LayoutParams lp = (LayoutParams) child.getLayoutParams();
135                 if (!lp.ignoreRightInset && lp.rightMargin != mRightInset) {
136                     lp.rightMargin = mRightInset;
137                     child.requestLayout();
138                 }
139             }
140         }
141     }
142 
143     @Override
generateLayoutParams(AttributeSet attrs)144     public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
145         return new LayoutParams(getContext(), attrs);
146     }
147 
148     @Override
generateDefaultLayoutParams()149     protected FrameLayout.LayoutParams generateDefaultLayoutParams() {
150         return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
151     }
152 
153     @Override
onFinishInflate()154     protected void onFinishInflate() {
155         super.onFinishInflate();
156         mStackScrollLayout = (NotificationStackScrollLayout) findViewById(
157                 R.id.notification_stack_scroller);
158         mNotificationPanel = (NotificationPanelView) findViewById(R.id.notification_panel);
159         mBrightnessMirror = findViewById(R.id.brightness_mirror);
160     }
161 
setService(PhoneStatusBar service)162     public void setService(PhoneStatusBar service) {
163         mService = service;
164         mDragDownHelper = new DragDownHelper(getContext(), this, mStackScrollLayout, mService);
165     }
166 
167     @Override
onAttachedToWindow()168     protected void onAttachedToWindow () {
169         super.onAttachedToWindow();
170 
171         // We need to ensure that our window doesn't suffer from overdraw which would normally
172         // occur if our window is translucent. Since we are drawing the whole window anyway with
173         // the scrim, we don't need the window to be cleared in the beginning.
174         if (mService.isScrimSrcModeEnabled()) {
175             IBinder windowToken = getWindowToken();
176             WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams();
177             lp.token = windowToken;
178             setLayoutParams(lp);
179             WindowManagerGlobal.getInstance().changeCanvasOpacity(windowToken, true);
180             setWillNotDraw(false);
181         } else {
182             setWillNotDraw(!DEBUG);
183         }
184     }
185 
186     @Override
dispatchKeyEvent(KeyEvent event)187     public boolean dispatchKeyEvent(KeyEvent event) {
188         boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
189         switch (event.getKeyCode()) {
190             case KeyEvent.KEYCODE_BACK:
191                 if (!down) {
192                     mService.onBackPressed();
193                 }
194                 return true;
195             case KeyEvent.KEYCODE_MENU:
196                 if (!down) {
197                     return mService.onMenuPressed();
198                 }
199             case KeyEvent.KEYCODE_SPACE:
200                 if (!down) {
201                     return mService.onSpacePressed();
202                 }
203                 break;
204             case KeyEvent.KEYCODE_VOLUME_DOWN:
205             case KeyEvent.KEYCODE_VOLUME_UP:
206                 if (mService.isDozing()) {
207                     MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, true);
208                     return true;
209                 }
210                 break;
211         }
212         if (mService.interceptMediaKey(event)) {
213             return true;
214         }
215         return super.dispatchKeyEvent(event);
216     }
217 
218     @Override
dispatchTouchEvent(MotionEvent ev)219     public boolean dispatchTouchEvent(MotionEvent ev) {
220         mFalsingManager.onTouchEvent(ev, getWidth(), getHeight());
221         if (mBrightnessMirror != null && mBrightnessMirror.getVisibility() == VISIBLE) {
222             // Disallow new pointers while the brightness mirror is visible. This is so that you
223             // can't touch anything other than the brightness slider while the mirror is showing
224             // and the rest of the panel is transparent.
225             if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
226                 return false;
227             }
228         }
229         if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
230             mStackScrollLayout.closeControlsIfOutsideTouch(ev);
231         }
232 
233         return super.dispatchTouchEvent(ev);
234     }
235 
236     @Override
onInterceptTouchEvent(MotionEvent ev)237     public boolean onInterceptTouchEvent(MotionEvent ev) {
238         boolean intercept = false;
239         if (mNotificationPanel.isFullyExpanded()
240                 && mStackScrollLayout.getVisibility() == View.VISIBLE
241                 && mService.getBarState() == StatusBarState.KEYGUARD
242                 && !mService.isBouncerShowing()) {
243             intercept = mDragDownHelper.onInterceptTouchEvent(ev);
244             // wake up on a touch down event, if dozing
245             if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
246                 mService.wakeUpIfDozing(ev.getEventTime(), ev);
247             }
248         }
249         if (!intercept) {
250             super.onInterceptTouchEvent(ev);
251         }
252         if (intercept) {
253             MotionEvent cancellation = MotionEvent.obtain(ev);
254             cancellation.setAction(MotionEvent.ACTION_CANCEL);
255             mStackScrollLayout.onInterceptTouchEvent(cancellation);
256             mNotificationPanel.onInterceptTouchEvent(cancellation);
257             cancellation.recycle();
258         }
259         return intercept;
260     }
261 
262     @Override
onTouchEvent(MotionEvent ev)263     public boolean onTouchEvent(MotionEvent ev) {
264         boolean handled = false;
265         if (mService.getBarState() == StatusBarState.KEYGUARD) {
266             handled = mDragDownHelper.onTouchEvent(ev);
267         }
268         if (!handled) {
269             handled = super.onTouchEvent(ev);
270         }
271         final int action = ev.getAction();
272         if (!handled && (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)) {
273             mService.setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
274         }
275         return handled;
276     }
277 
278     @Override
onDraw(Canvas canvas)279     public void onDraw(Canvas canvas) {
280         super.onDraw(canvas);
281         if (mService.isScrimSrcModeEnabled()) {
282             // We need to ensure that our window is always drawn fully even when we have paddings,
283             // since we simulate it to be opaque.
284             int paddedBottom = getHeight() - getPaddingBottom();
285             int paddedRight = getWidth() - getPaddingRight();
286             if (getPaddingTop() != 0) {
287                 canvas.drawRect(0, 0, getWidth(), getPaddingTop(), mTransparentSrcPaint);
288             }
289             if (getPaddingBottom() != 0) {
290                 canvas.drawRect(0, paddedBottom, getWidth(), getHeight(), mTransparentSrcPaint);
291             }
292             if (getPaddingLeft() != 0) {
293                 canvas.drawRect(0, getPaddingTop(), getPaddingLeft(), paddedBottom,
294                         mTransparentSrcPaint);
295             }
296             if (getPaddingRight() != 0) {
297                 canvas.drawRect(paddedRight, getPaddingTop(), getWidth(), paddedBottom,
298                         mTransparentSrcPaint);
299             }
300         }
301         if (DEBUG) {
302             Paint pt = new Paint();
303             pt.setColor(0x80FFFF00);
304             pt.setStrokeWidth(12.0f);
305             pt.setStyle(Paint.Style.STROKE);
306             canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), pt);
307         }
308     }
309 
cancelExpandHelper()310     public void cancelExpandHelper() {
311         if (mStackScrollLayout != null) {
312             mStackScrollLayout.cancelExpandHelper();
313         }
314     }
315 
316     public class LayoutParams extends FrameLayout.LayoutParams {
317 
318         public boolean ignoreRightInset;
319 
LayoutParams(int width, int height)320         public LayoutParams(int width, int height) {
321             super(width, height);
322         }
323 
LayoutParams(Context c, AttributeSet attrs)324         public LayoutParams(Context c, AttributeSet attrs) {
325             super(c, attrs);
326 
327             TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout);
328             ignoreRightInset = a.getBoolean(
329                     R.styleable.StatusBarWindowView_Layout_ignoreRightInset, false);
330             a.recycle();
331         }
332     }
333 
334     @Override
startActionModeForChild(View originalView, ActionMode.Callback callback, int type)335     public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback,
336             int type) {
337         if (type == ActionMode.TYPE_FLOATING) {
338             return startActionMode(originalView, callback, type);
339         }
340         return super.startActionModeForChild(originalView, callback, type);
341     }
342 
createFloatingActionMode( View originatingView, ActionMode.Callback2 callback)343     private ActionMode createFloatingActionMode(
344             View originatingView, ActionMode.Callback2 callback) {
345         if (mFloatingActionMode != null) {
346             mFloatingActionMode.finish();
347         }
348         cleanupFloatingActionModeViews();
349         final FloatingActionMode mode =
350                 new FloatingActionMode(mContext, callback, originatingView);
351         mFloatingActionModeOriginatingView = originatingView;
352         mFloatingToolbarPreDrawListener =
353                 new ViewTreeObserver.OnPreDrawListener() {
354                     @Override
355                     public boolean onPreDraw() {
356                         mode.updateViewLocationInWindow();
357                         return true;
358                     }
359                 };
360         return mode;
361     }
362 
setHandledFloatingActionMode(ActionMode mode)363     private void setHandledFloatingActionMode(ActionMode mode) {
364         mFloatingActionMode = mode;
365         mFloatingToolbar = new FloatingToolbar(mContext, mFakeWindow);
366         ((FloatingActionMode) mFloatingActionMode).setFloatingToolbar(mFloatingToolbar);
367         mFloatingActionMode.invalidate();  // Will show the floating toolbar if necessary.
368         mFloatingActionModeOriginatingView.getViewTreeObserver()
369                 .addOnPreDrawListener(mFloatingToolbarPreDrawListener);
370     }
371 
cleanupFloatingActionModeViews()372     private void cleanupFloatingActionModeViews() {
373         if (mFloatingToolbar != null) {
374             mFloatingToolbar.dismiss();
375             mFloatingToolbar = null;
376         }
377         if (mFloatingActionModeOriginatingView != null) {
378             if (mFloatingToolbarPreDrawListener != null) {
379                 mFloatingActionModeOriginatingView.getViewTreeObserver()
380                         .removeOnPreDrawListener(mFloatingToolbarPreDrawListener);
381                 mFloatingToolbarPreDrawListener = null;
382             }
383             mFloatingActionModeOriginatingView = null;
384         }
385     }
386 
startActionMode( View originatingView, ActionMode.Callback callback, int type)387     private ActionMode startActionMode(
388             View originatingView, ActionMode.Callback callback, int type) {
389         ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
390         ActionMode mode = createFloatingActionMode(originatingView, wrappedCallback);
391         if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
392             setHandledFloatingActionMode(mode);
393         } else {
394             mode = null;
395         }
396         return mode;
397     }
398 
399     private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
400         private final ActionMode.Callback mWrapped;
401 
ActionModeCallback2Wrapper(ActionMode.Callback wrapped)402         public ActionModeCallback2Wrapper(ActionMode.Callback wrapped) {
403             mWrapped = wrapped;
404         }
405 
onCreateActionMode(ActionMode mode, Menu menu)406         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
407             return mWrapped.onCreateActionMode(mode, menu);
408         }
409 
onPrepareActionMode(ActionMode mode, Menu menu)410         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
411             requestFitSystemWindows();
412             return mWrapped.onPrepareActionMode(mode, menu);
413         }
414 
onActionItemClicked(ActionMode mode, MenuItem item)415         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
416             return mWrapped.onActionItemClicked(mode, item);
417         }
418 
onDestroyActionMode(ActionMode mode)419         public void onDestroyActionMode(ActionMode mode) {
420             mWrapped.onDestroyActionMode(mode);
421             if (mode == mFloatingActionMode) {
422                 cleanupFloatingActionModeViews();
423                 mFloatingActionMode = null;
424             }
425             requestFitSystemWindows();
426         }
427 
428         @Override
onGetContentRect(ActionMode mode, View view, Rect outRect)429         public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
430             if (mWrapped instanceof ActionMode.Callback2) {
431                 ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect);
432             } else {
433                 super.onGetContentRect(mode, view, outRect);
434             }
435         }
436     }
437 
438     /**
439      * Minimal window to satisfy FloatingToolbar.
440      */
441     private Window mFakeWindow = new Window(mContext) {
442         @Override
443         public void takeSurface(SurfaceHolder.Callback2 callback) {
444         }
445 
446         @Override
447         public void takeInputQueue(InputQueue.Callback callback) {
448         }
449 
450         @Override
451         public boolean isFloating() {
452             return false;
453         }
454 
455         @Override
456         public void alwaysReadCloseOnTouchAttr() {
457         }
458 
459         @Override
460         public void setContentView(@LayoutRes int layoutResID) {
461         }
462 
463         @Override
464         public void setContentView(View view) {
465         }
466 
467         @Override
468         public void setContentView(View view, ViewGroup.LayoutParams params) {
469         }
470 
471         @Override
472         public void addContentView(View view, ViewGroup.LayoutParams params) {
473         }
474 
475         @Override
476         public void clearContentView() {
477         }
478 
479         @Override
480         public View getCurrentFocus() {
481             return null;
482         }
483 
484         @Override
485         public LayoutInflater getLayoutInflater() {
486             return null;
487         }
488 
489         @Override
490         public void setTitle(CharSequence title) {
491         }
492 
493         @Override
494         public void setTitleColor(@ColorInt int textColor) {
495         }
496 
497         @Override
498         public void openPanel(int featureId, KeyEvent event) {
499         }
500 
501         @Override
502         public void closePanel(int featureId) {
503         }
504 
505         @Override
506         public void togglePanel(int featureId, KeyEvent event) {
507         }
508 
509         @Override
510         public void invalidatePanelMenu(int featureId) {
511         }
512 
513         @Override
514         public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) {
515             return false;
516         }
517 
518         @Override
519         public boolean performPanelIdentifierAction(int featureId, int id, int flags) {
520             return false;
521         }
522 
523         @Override
524         public void closeAllPanels() {
525         }
526 
527         @Override
528         public boolean performContextMenuIdentifierAction(int id, int flags) {
529             return false;
530         }
531 
532         @Override
533         public void onConfigurationChanged(Configuration newConfig) {
534         }
535 
536         @Override
537         public void setBackgroundDrawable(Drawable drawable) {
538         }
539 
540         @Override
541         public void setFeatureDrawableResource(int featureId, @DrawableRes int resId) {
542         }
543 
544         @Override
545         public void setFeatureDrawableUri(int featureId, Uri uri) {
546         }
547 
548         @Override
549         public void setFeatureDrawable(int featureId, Drawable drawable) {
550         }
551 
552         @Override
553         public void setFeatureDrawableAlpha(int featureId, int alpha) {
554         }
555 
556         @Override
557         public void setFeatureInt(int featureId, int value) {
558         }
559 
560         @Override
561         public void takeKeyEvents(boolean get) {
562         }
563 
564         @Override
565         public boolean superDispatchKeyEvent(KeyEvent event) {
566             return false;
567         }
568 
569         @Override
570         public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
571             return false;
572         }
573 
574         @Override
575         public boolean superDispatchTouchEvent(MotionEvent event) {
576             return false;
577         }
578 
579         @Override
580         public boolean superDispatchTrackballEvent(MotionEvent event) {
581             return false;
582         }
583 
584         @Override
585         public boolean superDispatchGenericMotionEvent(MotionEvent event) {
586             return false;
587         }
588 
589         @Override
590         public View getDecorView() {
591             return StatusBarWindowView.this;
592         }
593 
594         @Override
595         public View peekDecorView() {
596             return null;
597         }
598 
599         @Override
600         public Bundle saveHierarchyState() {
601             return null;
602         }
603 
604         @Override
605         public void restoreHierarchyState(Bundle savedInstanceState) {
606         }
607 
608         @Override
609         protected void onActive() {
610         }
611 
612         @Override
613         public void setChildDrawable(int featureId, Drawable drawable) {
614         }
615 
616         @Override
617         public void setChildInt(int featureId, int value) {
618         }
619 
620         @Override
621         public boolean isShortcutKey(int keyCode, KeyEvent event) {
622             return false;
623         }
624 
625         @Override
626         public void setVolumeControlStream(int streamType) {
627         }
628 
629         @Override
630         public int getVolumeControlStream() {
631             return 0;
632         }
633 
634         @Override
635         public int getStatusBarColor() {
636             return 0;
637         }
638 
639         @Override
640         public void setStatusBarColor(@ColorInt int color) {
641         }
642 
643         @Override
644         public int getNavigationBarColor() {
645             return 0;
646         }
647 
648         @Override
649         public void setNavigationBarColor(@ColorInt int color) {
650         }
651 
652         @Override
653         public void setDecorCaptionShade(int decorCaptionShade) {
654         }
655 
656         @Override
657         public void setResizingCaptionDrawable(Drawable drawable) {
658         }
659 
660         @Override
661         public void onMultiWindowModeChanged() {
662         }
663 
664         @Override
665         public void reportActivityRelaunched() {
666         }
667     };
668 
669 }
670 
671