1 /*
2  * Copyright (C) 2014 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 android.annotation.ColorInt;
22 import android.content.Context;
23 import android.content.res.Configuration;
24 import android.content.res.Resources;
25 import android.graphics.Color;
26 import android.graphics.Rect;
27 import android.graphics.drawable.Drawable;
28 import android.util.AttributeSet;
29 import android.util.Pair;
30 import android.util.TypedValue;
31 import android.view.DisplayCutout;
32 import android.view.Gravity;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.view.ViewTreeObserver;
36 import android.view.WindowInsets;
37 import android.widget.ImageView;
38 import android.widget.LinearLayout;
39 import android.widget.RelativeLayout;
40 import android.widget.TextView;
41 
42 import com.android.settingslib.Utils;
43 import com.android.systemui.BatteryMeterView;
44 import com.android.systemui.Dependency;
45 import com.android.systemui.Interpolators;
46 import com.android.systemui.R;
47 import com.android.systemui.qs.QSPanel;
48 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
49 import com.android.systemui.statusbar.policy.BatteryController;
50 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
51 import com.android.systemui.statusbar.policy.ConfigurationController;
52 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
53 import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
54 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
55 import com.android.systemui.statusbar.policy.UserInfoController;
56 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
57 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
58 import com.android.systemui.statusbar.policy.UserSwitcherController;
59 
60 /**
61  * The header group on Keyguard.
62  */
63 public class KeyguardStatusBarView extends RelativeLayout
64         implements BatteryStateChangeCallback, OnUserInfoChangedListener, ConfigurationListener {
65 
66     private static final int LAYOUT_NONE = 0;
67     private static final int LAYOUT_CUTOUT = 1;
68     private static final int LAYOUT_NO_CUTOUT = 2;
69 
70     private boolean mBatteryCharging;
71     private boolean mKeyguardUserSwitcherShowing;
72     private boolean mBatteryListening;
73 
74     private TextView mCarrierLabel;
75     private View mSystemIconsSuperContainer;
76     private MultiUserSwitch mMultiUserSwitch;
77     private ImageView mMultiUserAvatar;
78     private BatteryMeterView mBatteryView;
79 
80     private BatteryController mBatteryController;
81     private KeyguardUserSwitcher mKeyguardUserSwitcher;
82     private UserSwitcherController mUserSwitcherController;
83 
84     private int mSystemIconsSwitcherHiddenExpandedMargin;
85     private int mSystemIconsBaseMargin;
86     private View mSystemIconsContainer;
87     private TintedIconManager mIconManager;
88 
89     private View mCutoutSpace;
90     private ViewGroup mStatusIconArea;
91     private int mLayoutState = LAYOUT_NONE;
92 
93     /**
94      * Draw this many pixels into the left/right side of the cutout to optimally use the space
95      */
96     private int mCutoutSideNudge = 0;
97 
KeyguardStatusBarView(Context context, AttributeSet attrs)98     public KeyguardStatusBarView(Context context, AttributeSet attrs) {
99         super(context, attrs);
100     }
101 
102     @Override
onFinishInflate()103     protected void onFinishInflate() {
104         super.onFinishInflate();
105         mSystemIconsContainer = findViewById(R.id.system_icons_container);
106         mMultiUserSwitch = findViewById(R.id.multi_user_switch);
107         mMultiUserAvatar = findViewById(R.id.multi_user_avatar);
108         mCarrierLabel = findViewById(R.id.keyguard_carrier_text);
109         mBatteryView = mSystemIconsContainer.findViewById(R.id.battery);
110         mCutoutSpace = findViewById(R.id.cutout_space_view);
111         mStatusIconArea = findViewById(R.id.status_icon_area);
112 
113         loadDimens();
114         updateUserSwitcher();
115         mBatteryController = Dependency.get(BatteryController.class);
116     }
117 
118     @Override
onConfigurationChanged(Configuration newConfig)119     protected void onConfigurationChanged(Configuration newConfig) {
120         super.onConfigurationChanged(newConfig);
121 
122         MarginLayoutParams lp = (MarginLayoutParams) mMultiUserAvatar.getLayoutParams();
123         lp.width = lp.height = getResources().getDimensionPixelSize(
124                 R.dimen.multi_user_avatar_keyguard_size);
125         mMultiUserAvatar.setLayoutParams(lp);
126 
127         // Multi-user switch
128         lp = (MarginLayoutParams) mMultiUserSwitch.getLayoutParams();
129         lp.width = getResources().getDimensionPixelSize(
130                 R.dimen.multi_user_switch_width_keyguard);
131         lp.setMarginEnd(getResources().getDimensionPixelSize(
132                 R.dimen.multi_user_switch_keyguard_margin));
133         mMultiUserSwitch.setLayoutParams(lp);
134 
135         // System icons
136         lp = (MarginLayoutParams) mSystemIconsContainer.getLayoutParams();
137         lp.setMarginStart(getResources().getDimensionPixelSize(
138                 R.dimen.system_icons_super_container_margin_start));
139         mSystemIconsContainer.setLayoutParams(lp);
140         mSystemIconsContainer.setPaddingRelative(mSystemIconsContainer.getPaddingStart(),
141                 mSystemIconsContainer.getPaddingTop(),
142                 getResources().getDimensionPixelSize(R.dimen.system_icons_keyguard_padding_end),
143                 mSystemIconsContainer.getPaddingBottom());
144 
145         // Respect font size setting.
146         mCarrierLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
147                 getResources().getDimensionPixelSize(
148                         com.android.internal.R.dimen.text_size_small_material));
149         lp = (MarginLayoutParams) mCarrierLabel.getLayoutParams();
150         lp.setMarginStart(
151                 getResources().getDimensionPixelSize(R.dimen.keyguard_carrier_text_margin));
152         mCarrierLabel.setLayoutParams(lp);
153 
154         lp = (MarginLayoutParams) getLayoutParams();
155         lp.height =  getResources().getDimensionPixelSize(
156                 R.dimen.status_bar_header_height_keyguard);
157         setLayoutParams(lp);
158     }
159 
loadDimens()160     private void loadDimens() {
161         Resources res = getResources();
162         mSystemIconsSwitcherHiddenExpandedMargin = res.getDimensionPixelSize(
163                 R.dimen.system_icons_switcher_hidden_expanded_margin);
164         mSystemIconsBaseMargin = res.getDimensionPixelSize(
165                 R.dimen.system_icons_super_container_avatarless_margin_end);
166         mCutoutSideNudge = getResources().getDimensionPixelSize(
167                 R.dimen.display_cutout_margin_consumption);
168     }
169 
updateVisibilities()170     private void updateVisibilities() {
171         if (mMultiUserSwitch.getParent() != mStatusIconArea && !mKeyguardUserSwitcherShowing) {
172             if (mMultiUserSwitch.getParent() != null) {
173                 getOverlay().remove(mMultiUserSwitch);
174             }
175             mStatusIconArea.addView(mMultiUserSwitch, 0);
176         } else if (mMultiUserSwitch.getParent() == mStatusIconArea && mKeyguardUserSwitcherShowing) {
177             mStatusIconArea.removeView(mMultiUserSwitch);
178         }
179         if (mKeyguardUserSwitcher == null) {
180             // If we have no keyguard switcher, the screen width is under 600dp. In this case,
181             // we don't show the multi-user avatar unless there is more than 1 user on the device.
182             if (mUserSwitcherController != null
183                     && mUserSwitcherController.getSwitchableUserCount() > 1) {
184                 mMultiUserSwitch.setVisibility(View.VISIBLE);
185             } else {
186                 mMultiUserSwitch.setVisibility(View.GONE);
187             }
188         }
189         mBatteryView.setForceShowPercent(mBatteryCharging);
190     }
191 
updateSystemIconsLayoutParams()192     private void updateSystemIconsLayoutParams() {
193         LinearLayout.LayoutParams lp =
194                 (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
195         // If the avatar icon is gone, we need to have some end margin to display the system icons
196         // correctly.
197         int baseMarginEnd = mMultiUserSwitch.getVisibility() == View.GONE
198                 ? mSystemIconsBaseMargin
199                 : 0;
200         int marginEnd = mKeyguardUserSwitcherShowing ? mSystemIconsSwitcherHiddenExpandedMargin :
201                 baseMarginEnd;
202         if (marginEnd != lp.getMarginEnd()) {
203             lp.setMarginEnd(marginEnd);
204             mSystemIconsContainer.setLayoutParams(lp);
205         }
206     }
207 
208     @Override
onApplyWindowInsets(WindowInsets insets)209     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
210         mLayoutState = LAYOUT_NONE;
211         if (updateLayoutConsideringCutout()) {
212             requestLayout();
213         }
214         return super.onApplyWindowInsets(insets);
215     }
216 
updateLayoutConsideringCutout()217     private boolean updateLayoutConsideringCutout() {
218         DisplayCutout dc = getRootWindowInsets().getDisplayCutout();
219         Pair<Integer, Integer> cornerCutoutMargins =
220                 PhoneStatusBarView.cornerCutoutMargins(dc, getDisplay());
221         updateCornerCutoutPadding(cornerCutoutMargins);
222         if (dc == null || cornerCutoutMargins != null) {
223             return updateLayoutParamsNoCutout();
224         } else {
225             return updateLayoutParamsForCutout(dc);
226         }
227     }
228 
updateCornerCutoutPadding(Pair<Integer, Integer> cornerCutoutMargins)229     private void updateCornerCutoutPadding(Pair<Integer, Integer> cornerCutoutMargins) {
230         if (cornerCutoutMargins != null) {
231             setPadding(cornerCutoutMargins.first, 0, cornerCutoutMargins.second, 0);
232         } else {
233             setPadding(0, 0, 0, 0);
234         }
235     }
236 
updateLayoutParamsNoCutout()237     private boolean updateLayoutParamsNoCutout() {
238         if (mLayoutState == LAYOUT_NO_CUTOUT) {
239             return false;
240         }
241         mLayoutState = LAYOUT_NO_CUTOUT;
242 
243         if (mCutoutSpace != null) {
244             mCutoutSpace.setVisibility(View.GONE);
245         }
246 
247         RelativeLayout.LayoutParams lp = (LayoutParams) mCarrierLabel.getLayoutParams();
248         lp.addRule(RelativeLayout.START_OF, R.id.status_icon_area);
249 
250         lp = (LayoutParams) mStatusIconArea.getLayoutParams();
251         lp.removeRule(RelativeLayout.RIGHT_OF);
252         lp.width = LayoutParams.WRAP_CONTENT;
253 
254         LinearLayout.LayoutParams llp =
255                 (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
256         llp.setMarginStart(getResources().getDimensionPixelSize(
257                 R.dimen.system_icons_super_container_margin_start));
258         return true;
259     }
260 
updateLayoutParamsForCutout(DisplayCutout dc)261     private boolean updateLayoutParamsForCutout(DisplayCutout dc) {
262         if (mLayoutState == LAYOUT_CUTOUT) {
263             return false;
264         }
265         mLayoutState = LAYOUT_CUTOUT;
266 
267         if (mCutoutSpace == null) {
268             updateLayoutParamsNoCutout();
269         }
270 
271         Rect bounds = new Rect();
272         boundsFromDirection(dc, Gravity.TOP, bounds);
273 
274         mCutoutSpace.setVisibility(View.VISIBLE);
275         RelativeLayout.LayoutParams lp = (LayoutParams) mCutoutSpace.getLayoutParams();
276         bounds.left = bounds.left + mCutoutSideNudge;
277         bounds.right = bounds.right - mCutoutSideNudge;
278         lp.width = bounds.width();
279         lp.height = bounds.height();
280         lp.addRule(RelativeLayout.CENTER_IN_PARENT);
281 
282         lp = (LayoutParams) mCarrierLabel.getLayoutParams();
283         lp.addRule(RelativeLayout.START_OF, R.id.cutout_space_view);
284 
285         lp = (LayoutParams) mStatusIconArea.getLayoutParams();
286         lp.addRule(RelativeLayout.RIGHT_OF, R.id.cutout_space_view);
287         lp.width = LayoutParams.MATCH_PARENT;
288 
289         LinearLayout.LayoutParams llp =
290                 (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
291         llp.setMarginStart(0);
292         return true;
293     }
294 
setListening(boolean listening)295     public void setListening(boolean listening) {
296         if (listening == mBatteryListening) {
297             return;
298         }
299         mBatteryListening = listening;
300         if (mBatteryListening) {
301             mBatteryController.addCallback(this);
302         } else {
303             mBatteryController.removeCallback(this);
304         }
305     }
306 
updateUserSwitcher()307     private void updateUserSwitcher() {
308         boolean keyguardSwitcherAvailable = mKeyguardUserSwitcher != null;
309         mMultiUserSwitch.setClickable(keyguardSwitcherAvailable);
310         mMultiUserSwitch.setFocusable(keyguardSwitcherAvailable);
311         mMultiUserSwitch.setKeyguardMode(keyguardSwitcherAvailable);
312     }
313 
314     @Override
onAttachedToWindow()315     protected void onAttachedToWindow() {
316         super.onAttachedToWindow();
317         UserInfoController userInfoController = Dependency.get(UserInfoController.class);
318         userInfoController.addCallback(this);
319         mUserSwitcherController = Dependency.get(UserSwitcherController.class);
320         mMultiUserSwitch.setUserSwitcherController(mUserSwitcherController);
321         userInfoController.reloadUserInfo();
322         Dependency.get(ConfigurationController.class).addCallback(this);
323         mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
324         Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
325         onThemeChanged();
326     }
327 
328     @Override
onDetachedFromWindow()329     protected void onDetachedFromWindow() {
330         super.onDetachedFromWindow();
331         Dependency.get(UserInfoController.class).removeCallback(this);
332         Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager);
333         Dependency.get(ConfigurationController.class).removeCallback(this);
334     }
335 
336     @Override
onUserInfoChanged(String name, Drawable picture, String userAccount)337     public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
338         mMultiUserAvatar.setImageDrawable(picture);
339     }
340 
setQSPanel(QSPanel qsp)341     public void setQSPanel(QSPanel qsp) {
342         mMultiUserSwitch.setQsPanel(qsp);
343     }
344 
345     @Override
onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging)346     public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
347         if (mBatteryCharging != charging) {
348             mBatteryCharging = charging;
349             updateVisibilities();
350         }
351     }
352 
353     @Override
onPowerSaveChanged(boolean isPowerSave)354     public void onPowerSaveChanged(boolean isPowerSave) {
355         // could not care less
356     }
357 
setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher)358     public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
359         mKeyguardUserSwitcher = keyguardUserSwitcher;
360         mMultiUserSwitch.setKeyguardUserSwitcher(keyguardUserSwitcher);
361         updateUserSwitcher();
362     }
363 
setKeyguardUserSwitcherShowing(boolean showing, boolean animate)364     public void setKeyguardUserSwitcherShowing(boolean showing, boolean animate) {
365         mKeyguardUserSwitcherShowing = showing;
366         if (animate) {
367             animateNextLayoutChange();
368         }
369         updateVisibilities();
370         updateLayoutConsideringCutout();
371         updateSystemIconsLayoutParams();
372     }
373 
animateNextLayoutChange()374     private void animateNextLayoutChange() {
375         final int systemIconsCurrentX = mSystemIconsContainer.getLeft();
376         final boolean userSwitcherVisible = mMultiUserSwitch.getParent() == mStatusIconArea;
377         getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
378             @Override
379             public boolean onPreDraw() {
380                 getViewTreeObserver().removeOnPreDrawListener(this);
381                 boolean userSwitcherHiding = userSwitcherVisible
382                         && mMultiUserSwitch.getParent() != mStatusIconArea;
383                 mSystemIconsContainer.setX(systemIconsCurrentX);
384                 mSystemIconsContainer.animate()
385                         .translationX(0)
386                         .setDuration(400)
387                         .setStartDelay(userSwitcherHiding ? 300 : 0)
388                         .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
389                         .start();
390                 if (userSwitcherHiding) {
391                     getOverlay().add(mMultiUserSwitch);
392                     mMultiUserSwitch.animate()
393                             .alpha(0f)
394                             .setDuration(300)
395                             .setStartDelay(0)
396                             .setInterpolator(Interpolators.ALPHA_OUT)
397                             .withEndAction(() -> {
398                                 mMultiUserSwitch.setAlpha(1f);
399                                 getOverlay().remove(mMultiUserSwitch);
400                             })
401                             .start();
402 
403                 } else {
404                     mMultiUserSwitch.setAlpha(0f);
405                     mMultiUserSwitch.animate()
406                             .alpha(1f)
407                             .setDuration(300)
408                             .setStartDelay(200)
409                             .setInterpolator(Interpolators.ALPHA_IN);
410                 }
411                 return true;
412             }
413         });
414 
415     }
416 
417     @Override
setVisibility(int visibility)418     public void setVisibility(int visibility) {
419         super.setVisibility(visibility);
420         if (visibility != View.VISIBLE) {
421             mSystemIconsContainer.animate().cancel();
422             mSystemIconsContainer.setTranslationX(0);
423             mMultiUserSwitch.animate().cancel();
424             mMultiUserSwitch.setAlpha(1f);
425         } else {
426             updateVisibilities();
427             updateSystemIconsLayoutParams();
428         }
429     }
430 
431     @Override
hasOverlappingRendering()432     public boolean hasOverlappingRendering() {
433         return false;
434     }
435 
onThemeChanged()436     public void onThemeChanged() {
437         @ColorInt int textColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor);
438         @ColorInt int iconColor = Utils.getDefaultColor(mContext, Color.luminance(textColor) < 0.5 ?
439                 R.color.dark_mode_icon_color_single_tone :
440                 R.color.light_mode_icon_color_single_tone);
441         float intensity = textColor == Color.WHITE ? 0 : 1;
442         mCarrierLabel.setTextColor(iconColor);
443         mBatteryView.setFillColor(iconColor);
444         mIconManager.setTint(iconColor);
445         Rect tintArea = new Rect(0, 0, 0, 0);
446 
447         applyDarkness(R.id.battery, tintArea, intensity, iconColor);
448         applyDarkness(R.id.clock, tintArea, intensity, iconColor);
449         // Reload user avatar
450         ((UserInfoControllerImpl) Dependency.get(UserInfoController.class))
451                 .onDensityOrFontScaleChanged();
452     }
453 
applyDarkness(int id, Rect tintArea, float intensity, int color)454     private void applyDarkness(int id, Rect tintArea, float intensity, int color) {
455         View v = findViewById(id);
456         if (v instanceof DarkReceiver) {
457             ((DarkReceiver) v).onDarkChanged(tintArea, intensity, color);
458         }
459     }
460 }
461