• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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