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