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 HeadsUpStatusBarView(Context context)59 public HeadsUpStatusBarView(Context context) { 60 this(context, null); 61 } 62 HeadsUpStatusBarView(Context context, AttributeSet attrs)63 public HeadsUpStatusBarView(Context context, AttributeSet attrs) { 64 this(context, attrs, 0); 65 } 66 HeadsUpStatusBarView(Context context, AttributeSet attrs, int defStyleAttr)67 public HeadsUpStatusBarView(Context context, AttributeSet attrs, int defStyleAttr) { 68 this(context, attrs, defStyleAttr, 0); 69 } 70 HeadsUpStatusBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)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 updateMaxWidth()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 onMeasure(int widthMeasureSpec, int heightMeasureSpec)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 onConfigurationChanged(Configuration newConfig)105 protected void onConfigurationChanged(Configuration newConfig) { 106 super.onConfigurationChanged(newConfig); 107 updateMaxWidth(); 108 } 109 110 @VisibleForTesting HeadsUpStatusBarView(Context context, View iconPlaceholder, TextView textView)111 public HeadsUpStatusBarView(Context context, View iconPlaceholder, TextView textView) { 112 this(context); 113 mIconPlaceholder = iconPlaceholder; 114 mTextView = textView; 115 } 116 117 @Override onFinishInflate()118 protected void onFinishInflate() { 119 super.onFinishInflate(); 120 mIconPlaceholder = findViewById(R.id.icon_placeholder); 121 mTextView = findViewById(R.id.text); 122 } 123 setEntry(NotificationData.Entry entry)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 onLayout(boolean changed, int l, int t, int r, int b)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 */ setPanelTranslation(float translationX)180 public void setPanelTranslation(float translationX) { 181 if (isLayoutRtl()) { 182 setTranslationX(translationX + mCutOutInset); 183 } else { 184 setTranslationX(translationX - mCutOutInset); 185 } 186 updateDrawingRect(); 187 } 188 updateDrawingRect()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 fitSystemWindows(Rect insets)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 getShowingEntry()227 public NotificationData.Entry getShowingEntry() { 228 return mShowingEntry; 229 } 230 getIconDrawingRect()231 public Rect getIconDrawingRect() { 232 return mIconDrawingRect; 233 } 234 onDarkChanged(Rect area, float darkIntensity, int tint)235 public void onDarkChanged(Rect area, float darkIntensity, int tint) { 236 mTextView.setTextColor(DarkIconDispatcher.getTint(area, this, tint)); 237 } 238 setPublicMode(boolean publicMode)239 public void setPublicMode(boolean publicMode) { 240 mPublicMode = publicMode; 241 } 242 setOnDrawingRectChangedListener(Runnable onDrawingRectChangedListener)243 public void setOnDrawingRectChangedListener(Runnable onDrawingRectChangedListener) { 244 mOnDrawingRectChangedListener = onDrawingRectChangedListener; 245 } 246 getDisplaySize()247 private void getDisplaySize() { 248 if (mDisplaySize == null) { 249 mDisplaySize = new Point(); 250 } 251 getDisplay().getRealSize(mDisplaySize); 252 } 253 254 @Override onAttachedToWindow()255 protected void onAttachedToWindow() { 256 super.onAttachedToWindow(); 257 getDisplaySize(); 258 } 259 } 260