Home | History | Annotate | Download | only in phone
      1 /*
      2  * Copyright (C) 2008 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.content.res.Configuration.ORIENTATION_PORTRAIT;
     20 
     21 import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
     22 
     23 import android.annotation.Nullable;
     24 import android.content.Context;
     25 import android.content.res.Configuration;
     26 import android.graphics.Point;
     27 import android.graphics.Rect;
     28 import android.util.AttributeSet;
     29 import android.util.EventLog;
     30 import android.util.Pair;
     31 import android.view.Display;
     32 import android.view.DisplayCutout;
     33 import android.view.Gravity;
     34 import android.view.MotionEvent;
     35 import android.view.View;
     36 import android.view.ViewGroup;
     37 import android.view.WindowInsets;
     38 import android.view.accessibility.AccessibilityEvent;
     39 import android.widget.FrameLayout;
     40 import android.widget.LinearLayout;
     41 
     42 import com.android.systemui.Dependency;
     43 import com.android.systemui.EventLogTags;
     44 import com.android.systemui.R;
     45 import com.android.systemui.statusbar.policy.DarkIconDispatcher;
     46 import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
     47 
     48 import java.util.Objects;
     49 
     50 public class PhoneStatusBarView extends PanelBar {
     51     private static final String TAG = "PhoneStatusBarView";
     52     private static final boolean DEBUG = StatusBar.DEBUG;
     53     private static final boolean DEBUG_GESTURES = false;
     54     private static final int NO_VALUE = Integer.MIN_VALUE;
     55 
     56     StatusBar mBar;
     57 
     58     boolean mIsFullyOpenedPanel = false;
     59     private final PhoneStatusBarTransitions mBarTransitions;
     60     private ScrimController mScrimController;
     61     private float mMinFraction;
     62     private float mPanelFraction;
     63     private Runnable mHideExpandedRunnable = new Runnable() {
     64         @Override
     65         public void run() {
     66             if (mPanelFraction == 0.0f) {
     67                 mBar.makeExpandedInvisible();
     68             }
     69         }
     70     };
     71     private DarkReceiver mBattery;
     72     private int mLastOrientation;
     73     @Nullable
     74     private View mCutoutSpace;
     75     @Nullable
     76     private DisplayCutout mDisplayCutout;
     77     /**
     78      * Draw this many pixels into the left/right side of the cutout to optimally use the space
     79      */
     80     private int mCutoutSideNudge = 0;
     81 
     82     public PhoneStatusBarView(Context context, AttributeSet attrs) {
     83         super(context, attrs);
     84 
     85         mBarTransitions = new PhoneStatusBarTransitions(this);
     86     }
     87 
     88     public BarTransitions getBarTransitions() {
     89         return mBarTransitions;
     90     }
     91 
     92     public void setBar(StatusBar bar) {
     93         mBar = bar;
     94     }
     95 
     96     public void setScrimController(ScrimController scrimController) {
     97         mScrimController = scrimController;
     98     }
     99 
    100     @Override
    101     public void onFinishInflate() {
    102         mBarTransitions.init();
    103         mBattery = findViewById(R.id.battery);
    104         mCutoutSpace = findViewById(R.id.cutout_space_view);
    105 
    106         updateResources();
    107     }
    108 
    109     @Override
    110     protected void onAttachedToWindow() {
    111         super.onAttachedToWindow();
    112         // Always have Battery meters in the status bar observe the dark/light modes.
    113         Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mBattery);
    114         if (updateOrientationAndCutout(getResources().getConfiguration().orientation)) {
    115             updateLayoutForCutout();
    116         }
    117     }
    118 
    119     @Override
    120     protected void onDetachedFromWindow() {
    121         super.onDetachedFromWindow();
    122         Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mBattery);
    123         mDisplayCutout = null;
    124     }
    125 
    126     @Override
    127     protected void onConfigurationChanged(Configuration newConfig) {
    128         super.onConfigurationChanged(newConfig);
    129 
    130         // May trigger cutout space layout-ing
    131         if (updateOrientationAndCutout(newConfig.orientation)) {
    132             updateLayoutForCutout();
    133             requestLayout();
    134         }
    135     }
    136 
    137     @Override
    138     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
    139         if (updateOrientationAndCutout(mLastOrientation)) {
    140             updateLayoutForCutout();
    141             requestLayout();
    142         }
    143         return super.onApplyWindowInsets(insets);
    144     }
    145 
    146     /**
    147      *
    148      * @param newOrientation may pass NO_VALUE for no change
    149      * @return boolean indicating if we need to update the cutout location / margins
    150      */
    151     private boolean updateOrientationAndCutout(int newOrientation) {
    152         boolean changed = false;
    153         if (newOrientation != NO_VALUE) {
    154             if (mLastOrientation != newOrientation) {
    155                 changed = true;
    156                 mLastOrientation = newOrientation;
    157             }
    158         }
    159 
    160         if (!Objects.equals(getRootWindowInsets().getDisplayCutout(), mDisplayCutout)) {
    161             changed = true;
    162             mDisplayCutout = getRootWindowInsets().getDisplayCutout();
    163         }
    164 
    165         return changed;
    166     }
    167 
    168     @Override
    169     public boolean panelEnabled() {
    170         return mBar.panelsEnabled();
    171     }
    172 
    173     @Override
    174     public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
    175         if (super.onRequestSendAccessibilityEventInternal(child, event)) {
    176             // The status bar is very small so augment the view that the user is touching
    177             // with the content of the status bar a whole. This way an accessibility service
    178             // may announce the current item as well as the entire content if appropriate.
    179             AccessibilityEvent record = AccessibilityEvent.obtain();
    180             onInitializeAccessibilityEvent(record);
    181             dispatchPopulateAccessibilityEvent(record);
    182             event.appendRecord(record);
    183             return true;
    184         }
    185         return false;
    186     }
    187 
    188     @Override
    189     public void onPanelPeeked() {
    190         super.onPanelPeeked();
    191         mBar.makeExpandedVisible(false);
    192     }
    193 
    194     @Override
    195     public void onPanelCollapsed() {
    196         super.onPanelCollapsed();
    197         // Close the status bar in the next frame so we can show the end of the animation.
    198         post(mHideExpandedRunnable);
    199         mIsFullyOpenedPanel = false;
    200     }
    201 
    202     public void removePendingHideExpandedRunnables() {
    203         removeCallbacks(mHideExpandedRunnable);
    204     }
    205 
    206     @Override
    207     public void onPanelFullyOpened() {
    208         super.onPanelFullyOpened();
    209         if (!mIsFullyOpenedPanel) {
    210             mPanel.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
    211         }
    212         mIsFullyOpenedPanel = true;
    213     }
    214 
    215     @Override
    216     public boolean onTouchEvent(MotionEvent event) {
    217         boolean barConsumedEvent = mBar.interceptTouchEvent(event);
    218 
    219         if (DEBUG_GESTURES) {
    220             if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
    221                 EventLog.writeEvent(EventLogTags.SYSUI_PANELBAR_TOUCH,
    222                         event.getActionMasked(), (int) event.getX(), (int) event.getY(),
    223                         barConsumedEvent ? 1 : 0);
    224             }
    225         }
    226 
    227         return barConsumedEvent || super.onTouchEvent(event);
    228     }
    229 
    230     @Override
    231     public void onTrackingStarted() {
    232         super.onTrackingStarted();
    233         mBar.onTrackingStarted();
    234         mScrimController.onTrackingStarted();
    235         removePendingHideExpandedRunnables();
    236     }
    237 
    238     @Override
    239     public void onClosingFinished() {
    240         super.onClosingFinished();
    241         mBar.onClosingFinished();
    242     }
    243 
    244     @Override
    245     public void onTrackingStopped(boolean expand) {
    246         super.onTrackingStopped(expand);
    247         mBar.onTrackingStopped(expand);
    248     }
    249 
    250     @Override
    251     public void onExpandingFinished() {
    252         super.onExpandingFinished();
    253         mScrimController.onExpandingFinished();
    254     }
    255 
    256     @Override
    257     public boolean onInterceptTouchEvent(MotionEvent event) {
    258         return mBar.interceptTouchEvent(event) || super.onInterceptTouchEvent(event);
    259     }
    260 
    261     @Override
    262     public void panelScrimMinFractionChanged(float minFraction) {
    263         if (mMinFraction != minFraction) {
    264             mMinFraction = minFraction;
    265             updateScrimFraction();
    266         }
    267     }
    268 
    269     @Override
    270     public void panelExpansionChanged(float frac, boolean expanded) {
    271         super.panelExpansionChanged(frac, expanded);
    272         mPanelFraction = frac;
    273         updateScrimFraction();
    274         if ((frac == 0 || frac == 1) && mBar.getNavigationBarView() != null) {
    275             mBar.getNavigationBarView().onPanelExpandedChange(expanded);
    276         }
    277     }
    278 
    279     private void updateScrimFraction() {
    280         float scrimFraction = mPanelFraction;
    281         if (mMinFraction < 1.0f) {
    282             scrimFraction = Math.max((mPanelFraction - mMinFraction) / (1.0f - mMinFraction),
    283                     0);
    284         }
    285         mScrimController.setPanelExpansion(scrimFraction);
    286     }
    287 
    288     public void updateResources() {
    289         mCutoutSideNudge = getResources().getDimensionPixelSize(
    290                 R.dimen.display_cutout_margin_consumption);
    291 
    292         ViewGroup.LayoutParams layoutParams = getLayoutParams();
    293         layoutParams.height = getResources().getDimensionPixelSize(
    294                 R.dimen.status_bar_height);
    295         setLayoutParams(layoutParams);
    296     }
    297 
    298     private void updateLayoutForCutout() {
    299         Pair<Integer, Integer> cornerCutoutMargins = cornerCutoutMargins(mDisplayCutout,
    300                 getDisplay());
    301         updateCutoutLocation(cornerCutoutMargins);
    302         updateSafeInsets(cornerCutoutMargins);
    303     }
    304 
    305     private void updateCutoutLocation(Pair<Integer, Integer> cornerCutoutMargins) {
    306         // Not all layouts have a cutout (e.g., Car)
    307         if (mCutoutSpace == null) {
    308             return;
    309         }
    310 
    311         if (mDisplayCutout == null || mDisplayCutout.isEmpty()
    312                     || mLastOrientation != ORIENTATION_PORTRAIT || cornerCutoutMargins != null) {
    313             mCutoutSpace.setVisibility(View.GONE);
    314             return;
    315         }
    316 
    317         mCutoutSpace.setVisibility(View.VISIBLE);
    318         LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mCutoutSpace.getLayoutParams();
    319 
    320         Rect bounds = new Rect();
    321         boundsFromDirection(mDisplayCutout, Gravity.TOP, bounds);
    322 
    323         bounds.left = bounds.left + mCutoutSideNudge;
    324         bounds.right = bounds.right - mCutoutSideNudge;
    325         lp.width = bounds.width();
    326         lp.height = bounds.height();
    327     }
    328 
    329     private void updateSafeInsets(Pair<Integer, Integer> cornerCutoutMargins) {
    330         // Depending on our rotation, we may have to work around a cutout in the middle of the view,
    331         // or letterboxing from the right or left sides.
    332 
    333         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
    334         if (mDisplayCutout == null) {
    335             lp.leftMargin = 0;
    336             lp.rightMargin = 0;
    337             return;
    338         }
    339 
    340         lp.leftMargin = mDisplayCutout.getSafeInsetLeft();
    341         lp.rightMargin = mDisplayCutout.getSafeInsetRight();
    342 
    343         if (cornerCutoutMargins != null) {
    344             lp.leftMargin = Math.max(lp.leftMargin, cornerCutoutMargins.first);
    345             lp.rightMargin = Math.max(lp.rightMargin, cornerCutoutMargins.second);
    346 
    347             // If we're already inset enough (e.g. on the status bar side), we can have 0 margin
    348             WindowInsets insets = getRootWindowInsets();
    349             int leftInset = insets.getSystemWindowInsetLeft();
    350             int rightInset = insets.getSystemWindowInsetRight();
    351             if (lp.leftMargin <= leftInset) {
    352                 lp.leftMargin = 0;
    353             }
    354             if (lp.rightMargin <= rightInset) {
    355                 lp.rightMargin = 0;
    356             }
    357 
    358         }
    359     }
    360 
    361     public static Pair<Integer, Integer> cornerCutoutMargins(DisplayCutout cutout,
    362             Display display) {
    363         if (cutout == null) {
    364             return null;
    365         }
    366         Point size = new Point();
    367         display.getRealSize(size);
    368 
    369         Rect bounds = new Rect();
    370         boundsFromDirection(cutout, Gravity.TOP, bounds);
    371 
    372         if (bounds.left <= 0) {
    373             return new Pair<>(bounds.right, 0);
    374         }
    375         if (bounds.right >= size.x) {
    376             return new Pair<>(0, size.x - bounds.left);
    377         }
    378         return null;
    379     }
    380 }
    381