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