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 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.content.res.Configuration; 23 import android.graphics.Rect; 24 import android.util.AttributeSet; 25 import android.util.Log; 26 import android.util.Pair; 27 import android.view.DisplayCutout; 28 import android.view.MotionEvent; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.view.WindowInsets; 32 import android.view.accessibility.AccessibilityEvent; 33 import android.widget.FrameLayout; 34 import android.widget.LinearLayout; 35 36 import com.android.internal.policy.SystemBarUtils; 37 import com.android.systemui.Dependency; 38 import com.android.systemui.Gefingerpoken; 39 import com.android.systemui.R; 40 import com.android.systemui.plugins.DarkIconDispatcher; 41 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; 42 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; 43 import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder; 44 import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel; 45 import com.android.systemui.util.leak.RotationUtils; 46 47 import java.util.Objects; 48 49 public class PhoneStatusBarView extends FrameLayout { 50 private static final String TAG = "PhoneStatusBarView"; 51 private final StatusBarContentInsetsProvider mContentInsetsProvider; 52 53 private DarkReceiver mBattery; 54 private DarkReceiver mClock; 55 private int mRotationOrientation = -1; 56 @Nullable 57 private View mCutoutSpace; 58 @Nullable 59 private DisplayCutout mDisplayCutout; 60 @Nullable 61 private Rect mDisplaySize; 62 private int mStatusBarHeight; 63 @Nullable 64 private Gefingerpoken mTouchEventHandler; 65 66 /** 67 * Draw this many pixels into the left/right side of the cutout to optimally use the space 68 */ 69 private int mCutoutSideNudge = 0; 70 PhoneStatusBarView(Context context, AttributeSet attrs)71 public PhoneStatusBarView(Context context, AttributeSet attrs) { 72 super(context, attrs); 73 mContentInsetsProvider = Dependency.get(StatusBarContentInsetsProvider.class); 74 } 75 setTouchEventHandler(Gefingerpoken handler)76 void setTouchEventHandler(Gefingerpoken handler) { 77 mTouchEventHandler = handler; 78 } 79 init(StatusBarUserChipViewModel viewModel)80 void init(StatusBarUserChipViewModel viewModel) { 81 StatusBarUserSwitcherContainer container = findViewById(R.id.user_switcher_container); 82 StatusBarUserChipViewBinder.bind(container, viewModel); 83 } 84 85 @Override onFinishInflate()86 public void onFinishInflate() { 87 super.onFinishInflate(); 88 mBattery = findViewById(R.id.battery); 89 mClock = findViewById(R.id.clock); 90 mCutoutSpace = findViewById(R.id.cutout_space_view); 91 92 updateResources(); 93 } 94 95 @Override onAttachedToWindow()96 protected void onAttachedToWindow() { 97 super.onAttachedToWindow(); 98 // Always have Battery meters in the status bar observe the dark/light modes. 99 Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mBattery); 100 Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mClock); 101 if (updateDisplayParameters()) { 102 updateLayoutForCutout(); 103 } 104 } 105 106 @Override onDetachedFromWindow()107 protected void onDetachedFromWindow() { 108 super.onDetachedFromWindow(); 109 Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mBattery); 110 Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mClock); 111 mDisplayCutout = null; 112 } 113 114 @Override onConfigurationChanged(Configuration newConfig)115 protected void onConfigurationChanged(Configuration newConfig) { 116 super.onConfigurationChanged(newConfig); 117 updateResources(); 118 119 // May trigger cutout space layout-ing 120 if (updateDisplayParameters()) { 121 updateLayoutForCutout(); 122 requestLayout(); 123 } 124 } 125 126 @Override onApplyWindowInsets(WindowInsets insets)127 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 128 if (updateDisplayParameters()) { 129 updateLayoutForCutout(); 130 requestLayout(); 131 } 132 return super.onApplyWindowInsets(insets); 133 } 134 135 /** 136 * @return boolean indicating if we need to update the cutout location / margins 137 */ updateDisplayParameters()138 private boolean updateDisplayParameters() { 139 boolean changed = false; 140 int newRotation = RotationUtils.getExactRotation(mContext); 141 if (newRotation != mRotationOrientation) { 142 changed = true; 143 mRotationOrientation = newRotation; 144 } 145 146 if (!Objects.equals(getRootWindowInsets().getDisplayCutout(), mDisplayCutout)) { 147 changed = true; 148 mDisplayCutout = getRootWindowInsets().getDisplayCutout(); 149 } 150 151 final Rect newSize = mContext.getResources().getConfiguration().windowConfiguration 152 .getMaxBounds(); 153 if (!Objects.equals(newSize, mDisplaySize)) { 154 changed = true; 155 mDisplaySize = newSize; 156 } 157 158 return changed; 159 } 160 161 @Override onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event)162 public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { 163 if (super.onRequestSendAccessibilityEventInternal(child, event)) { 164 // The status bar is very small so augment the view that the user is touching 165 // with the content of the status bar a whole. This way an accessibility service 166 // may announce the current item as well as the entire content if appropriate. 167 AccessibilityEvent record = AccessibilityEvent.obtain(); 168 onInitializeAccessibilityEvent(record); 169 dispatchPopulateAccessibilityEvent(record); 170 event.appendRecord(record); 171 return true; 172 } 173 return false; 174 } 175 176 @Override onTouchEvent(MotionEvent event)177 public boolean onTouchEvent(MotionEvent event) { 178 if (mTouchEventHandler == null) { 179 Log.w( 180 TAG, 181 String.format( 182 "onTouch: No touch handler provided; eating gesture at (%d,%d)", 183 (int) event.getX(), 184 (int) event.getY() 185 ) 186 ); 187 return true; 188 } 189 return mTouchEventHandler.onTouchEvent(event); 190 } 191 192 @Override onInterceptTouchEvent(MotionEvent event)193 public boolean onInterceptTouchEvent(MotionEvent event) { 194 mTouchEventHandler.onInterceptTouchEvent(event); 195 return super.onInterceptTouchEvent(event); 196 } 197 updateResources()198 public void updateResources() { 199 mCutoutSideNudge = getResources().getDimensionPixelSize( 200 R.dimen.display_cutout_margin_consumption); 201 202 updateStatusBarHeight(); 203 } 204 updateStatusBarHeight()205 private void updateStatusBarHeight() { 206 final int waterfallTopInset = 207 mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top; 208 ViewGroup.LayoutParams layoutParams = getLayoutParams(); 209 mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext); 210 layoutParams.height = mStatusBarHeight - waterfallTopInset; 211 212 int statusBarPaddingTop = getResources().getDimensionPixelSize( 213 R.dimen.status_bar_padding_top); 214 int statusBarPaddingStart = getResources().getDimensionPixelSize( 215 R.dimen.status_bar_padding_start); 216 int statusBarPaddingEnd = getResources().getDimensionPixelSize( 217 R.dimen.status_bar_padding_end); 218 219 View sbContents = findViewById(R.id.status_bar_contents); 220 sbContents.setPaddingRelative( 221 statusBarPaddingStart, 222 statusBarPaddingTop, 223 statusBarPaddingEnd, 224 0); 225 226 findViewById(R.id.notification_lights_out) 227 .setPaddingRelative(0, statusBarPaddingStart, 0, 0); 228 229 setLayoutParams(layoutParams); 230 } 231 updateLayoutForCutout()232 private void updateLayoutForCutout() { 233 updateStatusBarHeight(); 234 updateCutoutLocation(); 235 updateSafeInsets(); 236 } 237 updateCutoutLocation()238 private void updateCutoutLocation() { 239 // Not all layouts have a cutout (e.g., Car) 240 if (mCutoutSpace == null) { 241 return; 242 } 243 244 boolean hasCornerCutout = mContentInsetsProvider.currentRotationHasCornerCutout(); 245 if (mDisplayCutout == null || mDisplayCutout.isEmpty() || hasCornerCutout) { 246 mCutoutSpace.setVisibility(View.GONE); 247 return; 248 } 249 250 mCutoutSpace.setVisibility(View.VISIBLE); 251 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mCutoutSpace.getLayoutParams(); 252 253 Rect bounds = mDisplayCutout.getBoundingRectTop(); 254 255 bounds.left = bounds.left + mCutoutSideNudge; 256 bounds.right = bounds.right - mCutoutSideNudge; 257 lp.width = bounds.width(); 258 lp.height = bounds.height(); 259 } 260 updateSafeInsets()261 private void updateSafeInsets() { 262 Pair<Integer, Integer> insets = mContentInsetsProvider 263 .getStatusBarContentInsetsForCurrentRotation(); 264 265 setPadding( 266 insets.first, 267 getPaddingTop(), 268 insets.second, 269 getPaddingBottom()); 270 } 271 } 272