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 PhoneStatusBarView(Context context, AttributeSet attrs)82 public PhoneStatusBarView(Context context, AttributeSet attrs) { 83 super(context, attrs); 84 85 mBarTransitions = new PhoneStatusBarTransitions(this); 86 } 87 getBarTransitions()88 public BarTransitions getBarTransitions() { 89 return mBarTransitions; 90 } 91 setBar(StatusBar bar)92 public void setBar(StatusBar bar) { 93 mBar = bar; 94 } 95 setScrimController(ScrimController scrimController)96 public void setScrimController(ScrimController scrimController) { 97 mScrimController = scrimController; 98 } 99 100 @Override onFinishInflate()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 onAttachedToWindow()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 onDetachedFromWindow()120 protected void onDetachedFromWindow() { 121 super.onDetachedFromWindow(); 122 Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mBattery); 123 mDisplayCutout = null; 124 } 125 126 @Override onConfigurationChanged(Configuration newConfig)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 onApplyWindowInsets(WindowInsets insets)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 */ updateOrientationAndCutout(int newOrientation)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 panelEnabled()169 public boolean panelEnabled() { 170 return mBar.panelsEnabled(); 171 } 172 173 @Override onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event)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 onPanelPeeked()189 public void onPanelPeeked() { 190 super.onPanelPeeked(); 191 mBar.makeExpandedVisible(false); 192 } 193 194 @Override onPanelCollapsed()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 removePendingHideExpandedRunnables()202 public void removePendingHideExpandedRunnables() { 203 removeCallbacks(mHideExpandedRunnable); 204 } 205 206 @Override onPanelFullyOpened()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 onTouchEvent(MotionEvent event)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 onTrackingStarted()231 public void onTrackingStarted() { 232 super.onTrackingStarted(); 233 mBar.onTrackingStarted(); 234 mScrimController.onTrackingStarted(); 235 removePendingHideExpandedRunnables(); 236 } 237 238 @Override onClosingFinished()239 public void onClosingFinished() { 240 super.onClosingFinished(); 241 mBar.onClosingFinished(); 242 } 243 244 @Override onTrackingStopped(boolean expand)245 public void onTrackingStopped(boolean expand) { 246 super.onTrackingStopped(expand); 247 mBar.onTrackingStopped(expand); 248 } 249 250 @Override onExpandingFinished()251 public void onExpandingFinished() { 252 super.onExpandingFinished(); 253 mScrimController.onExpandingFinished(); 254 } 255 256 @Override onInterceptTouchEvent(MotionEvent event)257 public boolean onInterceptTouchEvent(MotionEvent event) { 258 return mBar.interceptTouchEvent(event) || super.onInterceptTouchEvent(event); 259 } 260 261 @Override panelScrimMinFractionChanged(float minFraction)262 public void panelScrimMinFractionChanged(float minFraction) { 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 mPanelFraction = frac; 273 updateScrimFraction(); 274 if ((frac == 0 || frac == 1) && mBar.getNavigationBarView() != null) { 275 mBar.getNavigationBarView().onPanelExpandedChange(expanded); 276 } 277 } 278 updateScrimFraction()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 updateResources()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 updateLayoutForCutout()298 private void updateLayoutForCutout() { 299 Pair<Integer, Integer> cornerCutoutMargins = cornerCutoutMargins(mDisplayCutout, 300 getDisplay()); 301 updateCutoutLocation(cornerCutoutMargins); 302 updateSafeInsets(cornerCutoutMargins); 303 } 304 updateCutoutLocation(Pair<Integer, Integer> cornerCutoutMargins)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 updateSafeInsets(Pair<Integer, Integer> cornerCutoutMargins)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 cornerCutoutMargins(DisplayCutout cutout, Display display)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