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