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