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