Home | History | Annotate | Download | only in statusbar
      1 /*
      2  * Copyright (C) 2018 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;
     18 
     19 import android.content.Context;
     20 import android.content.res.Configuration;
     21 import android.content.res.Resources;
     22 import android.graphics.Point;
     23 import android.graphics.Rect;
     24 import android.util.AttributeSet;
     25 import android.view.Display;
     26 import android.view.DisplayCutout;
     27 import android.view.View;
     28 import android.widget.TextView;
     29 
     30 import com.android.internal.annotations.VisibleForTesting;
     31 import com.android.keyguard.AlphaOptimizedLinearLayout;
     32 import com.android.systemui.R;
     33 import com.android.systemui.statusbar.policy.DarkIconDispatcher;
     34 
     35 import java.util.List;
     36 
     37 /**
     38  * The view in the statusBar that contains part of the heads-up information
     39  */
     40 public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout {
     41     private int mAbsoluteStartPadding;
     42     private int mEndMargin;
     43     private View mIconPlaceholder;
     44     private TextView mTextView;
     45     private NotificationData.Entry mShowingEntry;
     46     private Rect mLayoutedIconRect = new Rect();
     47     private int[] mTmpPosition = new int[2];
     48     private boolean mFirstLayout = true;
     49     private boolean mPublicMode;
     50     private int mMaxWidth;
     51     private View mRootView;
     52     private int mSysWinInset;
     53     private int mCutOutInset;
     54     private List<Rect> mCutOutBounds;
     55     private Rect mIconDrawingRect = new Rect();
     56     private Point mDisplaySize;
     57     private Runnable mOnDrawingRectChangedListener;
     58 
     59     public HeadsUpStatusBarView(Context context) {
     60         this(context, null);
     61     }
     62 
     63     public HeadsUpStatusBarView(Context context, AttributeSet attrs) {
     64         this(context, attrs, 0);
     65     }
     66 
     67     public HeadsUpStatusBarView(Context context, AttributeSet attrs, int defStyleAttr) {
     68         this(context, attrs, defStyleAttr, 0);
     69     }
     70 
     71     public HeadsUpStatusBarView(Context context, AttributeSet attrs, int defStyleAttr,
     72             int defStyleRes) {
     73         super(context, attrs, defStyleAttr, defStyleRes);
     74         Resources res = getResources();
     75         mAbsoluteStartPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings)
     76             + res.getDimensionPixelSize(
     77                     com.android.internal.R.dimen.notification_content_margin_start);
     78         mEndMargin = res.getDimensionPixelSize(
     79                 com.android.internal.R.dimen.notification_content_margin_end);
     80         setPaddingRelative(mAbsoluteStartPadding, 0, mEndMargin, 0);
     81         updateMaxWidth();
     82     }
     83 
     84     private void updateMaxWidth() {
     85         int maxWidth = getResources().getDimensionPixelSize(R.dimen.qs_panel_width);
     86         if (maxWidth != mMaxWidth) {
     87             // maxWidth doesn't work with fill_parent, let's manually make it at most as big as the
     88             // notification panel
     89             mMaxWidth = maxWidth;
     90             requestLayout();
     91         }
     92     }
     93 
     94     @Override
     95     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     96         if (mMaxWidth > 0) {
     97             int newSize = Math.min(MeasureSpec.getSize(widthMeasureSpec), mMaxWidth);
     98             widthMeasureSpec = MeasureSpec.makeMeasureSpec(newSize,
     99                     MeasureSpec.getMode(widthMeasureSpec));
    100         }
    101         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    102     }
    103 
    104     @Override
    105     protected void onConfigurationChanged(Configuration newConfig) {
    106         super.onConfigurationChanged(newConfig);
    107         updateMaxWidth();
    108     }
    109 
    110     @VisibleForTesting
    111     public HeadsUpStatusBarView(Context context, View iconPlaceholder, TextView textView) {
    112         this(context);
    113         mIconPlaceholder = iconPlaceholder;
    114         mTextView = textView;
    115     }
    116 
    117     @Override
    118     protected void onFinishInflate() {
    119         super.onFinishInflate();
    120         mIconPlaceholder = findViewById(R.id.icon_placeholder);
    121         mTextView = findViewById(R.id.text);
    122     }
    123 
    124     public void setEntry(NotificationData.Entry entry) {
    125         if (entry != null) {
    126             mShowingEntry = entry;
    127             CharSequence text = entry.headsUpStatusBarText;
    128             if (mPublicMode) {
    129                 text = entry.headsUpStatusBarTextPublic;
    130             }
    131             mTextView.setText(text);
    132         } else {
    133             mShowingEntry = null;
    134         }
    135     }
    136 
    137     @Override
    138     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    139         super.onLayout(changed, l, t, r, b);
    140         mIconPlaceholder.getLocationOnScreen(mTmpPosition);
    141         int left = (int) (mTmpPosition[0] - getTranslationX());
    142         int top = mTmpPosition[1];
    143         int right = left + mIconPlaceholder.getWidth();
    144         int bottom = top + mIconPlaceholder.getHeight();
    145         mLayoutedIconRect.set(left, top, right, bottom);
    146         updateDrawingRect();
    147         int targetPadding = mAbsoluteStartPadding + mSysWinInset + mCutOutInset;
    148         boolean isRtl = isLayoutRtl();
    149         int start = isRtl ? (mDisplaySize.x - right) : left;
    150 
    151         if (start != targetPadding) {
    152             if (mCutOutBounds != null) {
    153                 for (Rect cutOutRect : mCutOutBounds) {
    154                     int cutOutStart = (isRtl)
    155                             ? (mDisplaySize.x - cutOutRect.right) : cutOutRect.left;
    156                     if (start > cutOutStart) {
    157                         start -= cutOutRect.width();
    158                         break;
    159                     }
    160                 }
    161             }
    162 
    163             int newPadding = targetPadding - start + getPaddingStart();
    164             setPaddingRelative(newPadding, 0, mEndMargin, 0);
    165         }
    166         if (mFirstLayout) {
    167             // we need to do the padding calculation in the first frame, so the layout specified
    168             // our visibility to be INVISIBLE in the beginning. let's correct that and set it
    169             // to GONE.
    170             setVisibility(GONE);
    171             mFirstLayout = false;
    172         }
    173     }
    174 
    175     /** In order to do UI alignment, this view will be notified by
    176      * {@link com.android.systemui.statusbar.stack.NotificationStackScrollLayout}.
    177      * After scroller laid out, the scroller will tell this view about scroller's getX()
    178      * @param translationX how to translate the horizontal position
    179      */
    180     public void setPanelTranslation(float translationX) {
    181         if (isLayoutRtl()) {
    182             setTranslationX(translationX + mCutOutInset);
    183         } else {
    184             setTranslationX(translationX - mCutOutInset);
    185         }
    186         updateDrawingRect();
    187     }
    188 
    189     private void updateDrawingRect() {
    190         float oldLeft = mIconDrawingRect.left;
    191         mIconDrawingRect.set(mLayoutedIconRect);
    192         mIconDrawingRect.offset((int) getTranslationX(), 0);
    193         if (oldLeft != mIconDrawingRect.left && mOnDrawingRectChangedListener != null) {
    194             mOnDrawingRectChangedListener.run();
    195         }
    196     }
    197 
    198     @Override
    199     protected boolean fitSystemWindows(Rect insets) {
    200         boolean isRtl = isLayoutRtl();
    201         mSysWinInset = isRtl ? insets.right : insets.left;
    202         DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
    203         mCutOutInset = (displayCutout != null)
    204                 ? (isRtl ? displayCutout.getSafeInsetRight() : displayCutout.getSafeInsetLeft())
    205                 : 0;
    206 
    207         getDisplaySize();
    208 
    209         mCutOutBounds = null;
    210         if (displayCutout != null && displayCutout.getSafeInsetRight() == 0
    211                 && displayCutout.getSafeInsetLeft() == 0) {
    212             mCutOutBounds = displayCutout.getBoundingRects();
    213         }
    214 
    215         // For Double Cut Out mode, the System window navigation bar is at the right
    216         // side of the left cut out. In this condition, mSysWinInset include the left cut
    217         // out width so we set mCutOutInset to be 0. For RTL, the condition is the same.
    218         // The navigation bar is at the left side of the right cut out and include the
    219         // right cut out width.
    220         if (mSysWinInset != 0) {
    221             mCutOutInset = 0;
    222         }
    223 
    224         return super.fitSystemWindows(insets);
    225     }
    226 
    227     public NotificationData.Entry getShowingEntry() {
    228         return mShowingEntry;
    229     }
    230 
    231     public Rect getIconDrawingRect() {
    232         return mIconDrawingRect;
    233     }
    234 
    235     public void onDarkChanged(Rect area, float darkIntensity, int tint) {
    236         mTextView.setTextColor(DarkIconDispatcher.getTint(area, this, tint));
    237     }
    238 
    239     public void setPublicMode(boolean publicMode) {
    240         mPublicMode = publicMode;
    241     }
    242 
    243     public void setOnDrawingRectChangedListener(Runnable onDrawingRectChangedListener) {
    244         mOnDrawingRectChangedListener = onDrawingRectChangedListener;
    245     }
    246 
    247     private void getDisplaySize() {
    248         if (mDisplaySize == null) {
    249             mDisplaySize = new Point();
    250         }
    251         getDisplay().getRealSize(mDisplaySize);
    252     }
    253 
    254     @Override
    255     protected void onAttachedToWindow() {
    256         super.onAttachedToWindow();
    257         getDisplaySize();
    258     }
    259 }
    260