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