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 android.annotation.Nullable; 20 import android.content.Context; 21 import android.content.res.Configuration; 22 import android.graphics.Insets; 23 import android.graphics.Rect; 24 import android.util.AttributeSet; 25 import android.util.Log; 26 import android.view.DisplayCutout; 27 import android.view.MotionEvent; 28 import android.view.View; 29 import android.view.ViewGroup; 30 import android.view.WindowInsets; 31 import android.view.accessibility.AccessibilityEvent; 32 import android.widget.FrameLayout; 33 import android.widget.LinearLayout; 34 35 import androidx.annotation.NonNull; 36 37 import com.android.internal.policy.SystemBarUtils; 38 import com.android.systemui.Dependency; 39 import com.android.systemui.Flags; 40 import com.android.systemui.Gefingerpoken; 41 import com.android.systemui.res.R; 42 import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress; 43 import com.android.systemui.shade.StatusBarLongPressGestureDetector; 44 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; 45 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; 46 import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder; 47 import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel; 48 import com.android.systemui.util.leak.RotationUtils; 49 50 import java.util.Objects; 51 52 public class PhoneStatusBarView extends FrameLayout { 53 private static final String TAG = "PhoneStatusBarView"; 54 private final StatusBarWindowControllerStore mStatusBarWindowControllerStore; 55 56 private int mRotationOrientation = -1; 57 @Nullable 58 private View mCutoutSpace; 59 @Nullable 60 private DisplayCutout mDisplayCutout; 61 @Nullable 62 private Rect mDisplaySize; 63 private int mStatusBarHeight; 64 @Nullable 65 private Gefingerpoken mTouchEventHandler; 66 @Nullable 67 private HasCornerCutoutFetcher mHasCornerCutoutFetcher; 68 @Nullable 69 private InsetsFetcher mInsetsFetcher; 70 private int mDensity; 71 private float mFontScale; 72 private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector; 73 74 /** 75 * Draw this many pixels into the left/right side of the cutout to optimally use the space 76 */ 77 private int mCutoutSideNudge = 0; 78 PhoneStatusBarView(Context context, AttributeSet attrs)79 public PhoneStatusBarView(Context context, AttributeSet attrs) { 80 super(context, attrs); 81 mStatusBarWindowControllerStore = Dependency.get(StatusBarWindowControllerStore.class); 82 } 83 setLongPressGestureDetector( StatusBarLongPressGestureDetector statusBarLongPressGestureDetector)84 void setLongPressGestureDetector( 85 StatusBarLongPressGestureDetector statusBarLongPressGestureDetector) { 86 if (ShadeExpandsOnStatusBarLongPress.isEnabled()) { 87 mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector; 88 } 89 } 90 setTouchEventHandler(Gefingerpoken handler)91 void setTouchEventHandler(Gefingerpoken handler) { 92 mTouchEventHandler = handler; 93 } 94 setHasCornerCutoutFetcher(@onNull HasCornerCutoutFetcher cornerCutoutFetcher)95 void setHasCornerCutoutFetcher(@NonNull HasCornerCutoutFetcher cornerCutoutFetcher) { 96 mHasCornerCutoutFetcher = cornerCutoutFetcher; 97 updateCutoutLocation(); 98 } 99 setInsetsFetcher(@onNull InsetsFetcher insetsFetcher)100 void setInsetsFetcher(@NonNull InsetsFetcher insetsFetcher) { 101 mInsetsFetcher = insetsFetcher; 102 updateSafeInsets(); 103 } 104 init(StatusBarUserChipViewModel viewModel)105 void init(StatusBarUserChipViewModel viewModel) { 106 StatusBarUserSwitcherContainer container = findViewById(R.id.user_switcher_container); 107 StatusBarUserChipViewBinder.bind(container, viewModel); 108 } 109 110 @Override onFinishInflate()111 public void onFinishInflate() { 112 super.onFinishInflate(); 113 mCutoutSpace = findViewById(R.id.cutout_space_view); 114 115 updateResources(); 116 } 117 118 @Override onAttachedToWindow()119 protected void onAttachedToWindow() { 120 super.onAttachedToWindow(); 121 if (updateDisplayParameters()) { 122 updateLayoutForCutout(); 123 updateWindowHeight(); 124 } 125 } 126 127 @Override onDetachedFromWindow()128 protected void onDetachedFromWindow() { 129 super.onDetachedFromWindow(); 130 mDisplayCutout = null; 131 } 132 133 // Per b/300629388, we let the PhoneStatusBarView detect onConfigurationChanged to 134 // updateResources, instead of letting the PhoneStatusBarViewController detect onConfigChanged 135 // then notify PhoneStatusBarView. 136 @Override onConfigurationChanged(Configuration newConfig)137 protected void onConfigurationChanged(Configuration newConfig) { 138 super.onConfigurationChanged(newConfig); 139 updateResources(); 140 141 // May trigger cutout space layout-ing 142 if (updateDisplayParameters()) { 143 updateLayoutForCutout(); 144 requestLayout(); 145 } 146 updateWindowHeight(); 147 } 148 149 @Override onApplyWindowInsets(WindowInsets insets)150 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 151 if (updateDisplayParameters()) { 152 updateLayoutForCutout(); 153 requestLayout(); 154 } 155 return super.onApplyWindowInsets(insets); 156 } 157 158 /** 159 * @return boolean indicating if we need to update the cutout location / margins 160 */ updateDisplayParameters()161 private boolean updateDisplayParameters() { 162 boolean changed = false; 163 int newRotation = RotationUtils.getExactRotation(mContext); 164 if (newRotation != mRotationOrientation) { 165 changed = true; 166 mRotationOrientation = newRotation; 167 } 168 169 if (!Objects.equals(getRootWindowInsets().getDisplayCutout(), mDisplayCutout)) { 170 changed = true; 171 mDisplayCutout = getRootWindowInsets().getDisplayCutout(); 172 } 173 174 Configuration newConfiguration = mContext.getResources().getConfiguration(); 175 final Rect newSize = newConfiguration.windowConfiguration.getMaxBounds(); 176 if (!Objects.equals(newSize, mDisplaySize)) { 177 changed = true; 178 mDisplaySize = newSize; 179 } 180 181 int density = newConfiguration.densityDpi; 182 if (density != mDensity) { 183 changed = true; 184 mDensity = density; 185 } 186 float fontScale = newConfiguration.fontScale; 187 if (fontScale != mFontScale) { 188 changed = true; 189 mFontScale = fontScale; 190 } 191 return changed; 192 } 193 194 @Override onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event)195 public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { 196 if (super.onRequestSendAccessibilityEventInternal(child, event)) { 197 // The status bar is very small so augment the view that the user is touching 198 // with the content of the status bar a whole. This way an accessibility service 199 // may announce the current item as well as the entire content if appropriate. 200 AccessibilityEvent record = AccessibilityEvent.obtain(); 201 onInitializeAccessibilityEvent(record); 202 dispatchPopulateAccessibilityEvent(record); 203 event.appendRecord(record); 204 return true; 205 } 206 return false; 207 } 208 209 @Override onTouchEvent(MotionEvent event)210 public boolean onTouchEvent(MotionEvent event) { 211 if (ShadeExpandsOnStatusBarLongPress.isEnabled() 212 && mStatusBarLongPressGestureDetector != null) { 213 mStatusBarLongPressGestureDetector.handleTouch(event); 214 } 215 if (mTouchEventHandler == null) { 216 Log.w( 217 TAG, 218 String.format( 219 "onTouch: No touch handler provided; eating gesture at (%d,%d)", 220 (int) event.getX(), 221 (int) event.getY() 222 ) 223 ); 224 return true; 225 } 226 return mTouchEventHandler.onTouchEvent(event); 227 } 228 229 @Override onInterceptTouchEvent(MotionEvent event)230 public boolean onInterceptTouchEvent(MotionEvent event) { 231 if (Flags.statusBarSwipeOverChip()) { 232 return mTouchEventHandler.onInterceptTouchEvent(event); 233 } else { 234 mTouchEventHandler.onInterceptTouchEvent(event); 235 return super.onInterceptTouchEvent(event); 236 } 237 } 238 updateResources()239 public void updateResources() { 240 mCutoutSideNudge = getResources().getDimensionPixelSize( 241 R.dimen.display_cutout_margin_consumption); 242 243 updateStatusBarHeight(); 244 } 245 updateStatusBarHeight()246 private void updateStatusBarHeight() { 247 final int waterfallTopInset = 248 mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top; 249 ViewGroup.LayoutParams layoutParams = getLayoutParams(); 250 mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext); 251 layoutParams.height = mStatusBarHeight - waterfallTopInset; 252 updateSystemIconsContainerHeight(); 253 updatePaddings(); 254 setLayoutParams(layoutParams); 255 } 256 updateSystemIconsContainerHeight()257 private void updateSystemIconsContainerHeight() { 258 View systemIconsContainer = findViewById(R.id.system_icons); 259 ViewGroup.LayoutParams layoutParams = systemIconsContainer.getLayoutParams(); 260 int newSystemIconsHeight = 261 getResources().getDimensionPixelSize(R.dimen.status_bar_system_icons_height); 262 if (layoutParams.height != newSystemIconsHeight) { 263 layoutParams.height = newSystemIconsHeight; 264 systemIconsContainer.setLayoutParams(layoutParams); 265 } 266 } 267 updatePaddings()268 private void updatePaddings() { 269 int statusBarPaddingStart = getResources().getDimensionPixelSize( 270 R.dimen.status_bar_padding_start); 271 272 findViewById(R.id.status_bar_contents).setPaddingRelative( 273 statusBarPaddingStart, 274 getResources().getDimensionPixelSize(R.dimen.status_bar_padding_top), 275 getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end), 276 0); 277 278 findViewById(R.id.notification_lights_out) 279 .setPaddingRelative(0, statusBarPaddingStart, 0, 0); 280 281 findViewById(R.id.system_icons).setPaddingRelative( 282 getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_start), 283 getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_top), 284 getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_end), 285 getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_bottom) 286 ); 287 } 288 updateLayoutForCutout()289 private void updateLayoutForCutout() { 290 updateStatusBarHeight(); 291 updateCutoutLocation(); 292 updateSafeInsets(); 293 } 294 updateCutoutLocation()295 private void updateCutoutLocation() { 296 // Not all layouts have a cutout (e.g., Car) 297 if (mCutoutSpace == null) { 298 return; 299 } 300 301 boolean hasCornerCutout; 302 if (mHasCornerCutoutFetcher != null) { 303 hasCornerCutout = mHasCornerCutoutFetcher.fetchHasCornerCutout(); 304 } else { 305 Log.e(TAG, "mHasCornerCutoutFetcher unexpectedly null"); 306 hasCornerCutout = true; 307 } 308 309 if (mDisplayCutout == null || mDisplayCutout.isEmpty() || hasCornerCutout) { 310 mCutoutSpace.setVisibility(View.GONE); 311 return; 312 } 313 314 mCutoutSpace.setVisibility(View.VISIBLE); 315 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mCutoutSpace.getLayoutParams(); 316 317 Rect bounds = mDisplayCutout.getBoundingRectTop(); 318 319 bounds.left = bounds.left + mCutoutSideNudge; 320 bounds.right = bounds.right - mCutoutSideNudge; 321 lp.width = bounds.width(); 322 lp.height = bounds.height(); 323 } 324 updateSafeInsets()325 private void updateSafeInsets() { 326 if (mInsetsFetcher == null) { 327 Log.e(TAG, "mInsetsFetcher unexpectedly null"); 328 return; 329 } 330 331 Insets insets = mInsetsFetcher.fetchInsets(); 332 setPadding( 333 insets.left, 334 insets.top, 335 insets.right, 336 getPaddingBottom()); 337 } 338 updateWindowHeight()339 private void updateWindowHeight() { 340 if (Flags.statusBarStopUpdatingWindowHeight()) { 341 return; 342 } 343 mStatusBarWindowControllerStore.getDefaultDisplay().refreshStatusBarHeight(); 344 } 345 346 interface HasCornerCutoutFetcher { fetchHasCornerCutout()347 boolean fetchHasCornerCutout(); 348 } 349 350 interface InsetsFetcher { fetchInsets()351 Insets fetchInsets(); 352 } 353 } 354