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