• 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 
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