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