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