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.DejankUtils.whitelistIpcs;
20 import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
21 import static com.android.systemui.util.Utils.getStatusBarHeaderHeightKeyguard;
22 
23 import android.annotation.ColorInt;
24 import android.content.Context;
25 import android.content.res.Configuration;
26 import android.content.res.Resources;
27 import android.graphics.Color;
28 import android.graphics.Rect;
29 import android.graphics.drawable.Drawable;
30 import android.os.UserManager;
31 import android.util.AttributeSet;
32 import android.util.Pair;
33 import android.util.TypedValue;
34 import android.view.DisplayCutout;
35 import android.view.Gravity;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.view.ViewTreeObserver;
39 import android.view.WindowInsets;
40 import android.widget.ImageView;
41 import android.widget.LinearLayout;
42 import android.widget.RelativeLayout;
43 import android.widget.TextView;
44 
45 import com.android.settingslib.Utils;
46 import com.android.systemui.R;
47 import com.android.systemui.animation.Interpolators;
48 import com.android.systemui.battery.BatteryMeterView;
49 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
50 
51 import java.io.FileDescriptor;
52 import java.io.PrintWriter;
53 
54 /**
55  * The header group on Keyguard.
56  */
57 public class KeyguardStatusBarView extends RelativeLayout {
58 
59     private static final int LAYOUT_NONE = 0;
60     private static final int LAYOUT_CUTOUT = 1;
61     private static final int LAYOUT_NO_CUTOUT = 2;
62 
63     private final Rect mEmptyRect = new Rect(0, 0, 0, 0);
64 
65     private boolean mShowPercentAvailable;
66     private boolean mBatteryCharging;
67 
68     private TextView mCarrierLabel;
69     private ImageView mMultiUserAvatar;
70     private BatteryMeterView mBatteryView;
71     private StatusIconContainer mStatusIconContainer;
72 
73     private boolean mKeyguardUserSwitcherEnabled;
74     private final UserManager mUserManager;
75 
76     private boolean mIsPrivacyDotEnabled;
77     private int mSystemIconsSwitcherHiddenExpandedMargin;
78     private int mStatusBarPaddingEnd;
79     private int mMinDotWidth;
80     private View mSystemIconsContainer;
81 
82     private View mCutoutSpace;
83     private ViewGroup mStatusIconArea;
84     private int mLayoutState = LAYOUT_NONE;
85 
86     /**
87      * Draw this many pixels into the left/right side of the cutout to optimally use the space
88      */
89     private int mCutoutSideNudge = 0;
90 
91     private DisplayCutout mDisplayCutout;
92     private int mRoundedCornerPadding = 0;
93     // right and left padding applied to this view to account for cutouts and rounded corners
94     private Pair<Integer, Integer> mPadding = new Pair(0, 0);
95 
96     /**
97      * The clipping on the top
98      */
99     private int mTopClipping;
100     private final Rect mClipRect = new Rect(0, 0, 0, 0);
101 
KeyguardStatusBarView(Context context, AttributeSet attrs)102     public KeyguardStatusBarView(Context context, AttributeSet attrs) {
103         super(context, attrs);
104         mUserManager = UserManager.get(getContext());
105     }
106 
107     @Override
onFinishInflate()108     protected void onFinishInflate() {
109         super.onFinishInflate();
110         mSystemIconsContainer = findViewById(R.id.system_icons_container);
111         mMultiUserAvatar = findViewById(R.id.multi_user_avatar);
112         mCarrierLabel = findViewById(R.id.keyguard_carrier_text);
113         mBatteryView = mSystemIconsContainer.findViewById(R.id.battery);
114         mCutoutSpace = findViewById(R.id.cutout_space_view);
115         mStatusIconArea = findViewById(R.id.status_icon_area);
116         mStatusIconContainer = findViewById(R.id.statusIcons);
117         mIsPrivacyDotEnabled = mContext.getResources().getBoolean(R.bool.config_enablePrivacyDot);
118         loadDimens();
119     }
120 
121     @Override
onConfigurationChanged(Configuration newConfig)122     protected void onConfigurationChanged(Configuration newConfig) {
123         super.onConfigurationChanged(newConfig);
124         loadDimens();
125 
126         MarginLayoutParams lp = (MarginLayoutParams) mMultiUserAvatar.getLayoutParams();
127         lp.width = lp.height = getResources().getDimensionPixelSize(
128                 R.dimen.multi_user_avatar_keyguard_size);
129         mMultiUserAvatar.setLayoutParams(lp);
130 
131         // System icons
132         updateSystemIconsLayoutParams();
133 
134         // mStatusIconArea
135         mStatusIconArea.setPaddingRelative(
136                 mStatusIconArea.getPaddingStart(),
137                 getResources().getDimensionPixelSize(R.dimen.status_bar_padding_top),
138                 mStatusIconArea.getPaddingEnd(),
139                 mStatusIconArea.getPaddingBottom()
140         );
141 
142         // mStatusIconContainer
143         mStatusIconContainer.setPaddingRelative(
144                 mStatusIconContainer.getPaddingStart(),
145                 mStatusIconContainer.getPaddingTop(),
146                 getResources().getDimensionPixelSize(R.dimen.signal_cluster_battery_padding),
147                 mStatusIconContainer.getPaddingBottom()
148         );
149 
150         // Respect font size setting.
151         mCarrierLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
152                 getResources().getDimensionPixelSize(
153                         com.android.internal.R.dimen.text_size_small_material));
154         lp = (MarginLayoutParams) mCarrierLabel.getLayoutParams();
155 
156         int marginStart = calculateMargin(
157                 getResources().getDimensionPixelSize(R.dimen.keyguard_carrier_text_margin),
158                 mPadding.first);
159         lp.setMarginStart(marginStart);
160 
161         mCarrierLabel.setLayoutParams(lp);
162         updateKeyguardStatusBarHeight();
163     }
164 
updateKeyguardStatusBarHeight()165     private void updateKeyguardStatusBarHeight() {
166         MarginLayoutParams lp =  (MarginLayoutParams) getLayoutParams();
167         lp.height = getStatusBarHeaderHeightKeyguard(mContext);
168         setLayoutParams(lp);
169     }
170 
loadDimens()171     void loadDimens() {
172         Resources res = getResources();
173         mSystemIconsSwitcherHiddenExpandedMargin = res.getDimensionPixelSize(
174                 R.dimen.system_icons_switcher_hidden_expanded_margin);
175         mStatusBarPaddingEnd = res.getDimensionPixelSize(
176                 R.dimen.status_bar_padding_end);
177         mMinDotWidth = res.getDimensionPixelSize(
178                 R.dimen.ongoing_appops_dot_min_padding);
179         mCutoutSideNudge = getResources().getDimensionPixelSize(
180                 R.dimen.display_cutout_margin_consumption);
181         mShowPercentAvailable = getContext().getResources().getBoolean(
182                 com.android.internal.R.bool.config_battery_percentage_setting_available);
183         mRoundedCornerPadding = res.getDimensionPixelSize(
184                 R.dimen.rounded_corner_content_padding);
185     }
186 
updateVisibilities()187     private void updateVisibilities() {
188         if (mMultiUserAvatar.getParent() != mStatusIconArea
189                 && !mKeyguardUserSwitcherEnabled) {
190             if (mMultiUserAvatar.getParent() != null) {
191                 getOverlay().remove(mMultiUserAvatar);
192             }
193             mStatusIconArea.addView(mMultiUserAvatar, 0);
194         } else if (mMultiUserAvatar.getParent() == mStatusIconArea
195                 && mKeyguardUserSwitcherEnabled) {
196             mStatusIconArea.removeView(mMultiUserAvatar);
197         }
198         if (!mKeyguardUserSwitcherEnabled) {
199             // If we have no keyguard switcher, the screen width is under 600dp. In this case,
200             // we only show the multi-user switch if it's enabled through UserManager as well as
201             // by the user.
202             // TODO(b/138661450) Move IPC calls to background
203             boolean isMultiUserEnabled = whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled(
204                     mContext.getResources().getBoolean(
205                             R.bool.qs_show_user_switcher_for_single_user)));
206             if (isMultiUserEnabled) {
207                 mMultiUserAvatar.setVisibility(View.VISIBLE);
208             } else {
209                 mMultiUserAvatar.setVisibility(View.GONE);
210             }
211         }
212         mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable);
213     }
214 
updateSystemIconsLayoutParams()215     private void updateSystemIconsLayoutParams() {
216         LinearLayout.LayoutParams lp =
217                 (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
218 
219         int marginStart = getResources().getDimensionPixelSize(
220                 R.dimen.system_icons_super_container_margin_start);
221 
222         // Use status_bar_padding_end to replace original
223         // system_icons_super_container_avatarless_margin_end to prevent different end alignment
224         // between PhoneStatusBarView and KeyguardStatusBarView
225         int baseMarginEnd = mStatusBarPaddingEnd;
226         int marginEnd =
227                 mKeyguardUserSwitcherEnabled ? mSystemIconsSwitcherHiddenExpandedMargin
228                         : baseMarginEnd;
229 
230         // Align PhoneStatusBar right margin/padding, only use
231         // 1. status bar layout: mPadding(consider round_corner + privacy dot)
232         // 2. icon container: R.dimen.status_bar_padding_end
233 
234         if (marginEnd != lp.getMarginEnd() || marginStart != lp.getMarginStart()) {
235             lp.setMarginStart(marginStart);
236             lp.setMarginEnd(marginEnd);
237             mSystemIconsContainer.setLayoutParams(lp);
238         }
239     }
240 
241     /** Should only be called from {@link KeyguardStatusBarViewController}. */
updateWindowInsets( WindowInsets insets, StatusBarContentInsetsProvider insetsProvider)242     WindowInsets updateWindowInsets(
243             WindowInsets insets,
244             StatusBarContentInsetsProvider insetsProvider) {
245         mLayoutState = LAYOUT_NONE;
246         if (updateLayoutConsideringCutout(insetsProvider)) {
247             requestLayout();
248         }
249         return super.onApplyWindowInsets(insets);
250     }
251 
updateLayoutConsideringCutout(StatusBarContentInsetsProvider insetsProvider)252     private boolean updateLayoutConsideringCutout(StatusBarContentInsetsProvider insetsProvider) {
253         mDisplayCutout = getRootWindowInsets().getDisplayCutout();
254         updateKeyguardStatusBarHeight();
255         updatePadding(insetsProvider);
256         if (mDisplayCutout == null || insetsProvider.currentRotationHasCornerCutout()) {
257             return updateLayoutParamsNoCutout();
258         } else {
259             return updateLayoutParamsForCutout();
260         }
261     }
262 
updatePadding(StatusBarContentInsetsProvider insetsProvider)263     private void updatePadding(StatusBarContentInsetsProvider insetsProvider) {
264         final int waterfallTop =
265                 mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top;
266         mPadding = insetsProvider.getStatusBarContentInsetsForCurrentRotation();
267 
268         // consider privacy dot space
269         final int minLeft = (isLayoutRtl() && mIsPrivacyDotEnabled)
270                 ? Math.max(mMinDotWidth, mPadding.first) : mPadding.first;
271         final int minRight = (!isLayoutRtl() && mIsPrivacyDotEnabled)
272                 ? Math.max(mMinDotWidth, mPadding.second) : mPadding.second;
273 
274         setPadding(minLeft, waterfallTop, minRight, 0);
275     }
276 
updateLayoutParamsNoCutout()277     private boolean updateLayoutParamsNoCutout() {
278         if (mLayoutState == LAYOUT_NO_CUTOUT) {
279             return false;
280         }
281         mLayoutState = LAYOUT_NO_CUTOUT;
282 
283         if (mCutoutSpace != null) {
284             mCutoutSpace.setVisibility(View.GONE);
285         }
286 
287         RelativeLayout.LayoutParams lp = (LayoutParams) mCarrierLabel.getLayoutParams();
288         lp.addRule(RelativeLayout.START_OF, R.id.status_icon_area);
289 
290         lp = (LayoutParams) mStatusIconArea.getLayoutParams();
291         lp.removeRule(RelativeLayout.RIGHT_OF);
292         lp.width = LayoutParams.WRAP_CONTENT;
293 
294         LinearLayout.LayoutParams llp =
295                 (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
296         llp.setMarginStart(getResources().getDimensionPixelSize(
297                 R.dimen.system_icons_super_container_margin_start));
298         return true;
299     }
300 
updateLayoutParamsForCutout()301     private boolean updateLayoutParamsForCutout() {
302         if (mLayoutState == LAYOUT_CUTOUT) {
303             return false;
304         }
305         mLayoutState = LAYOUT_CUTOUT;
306 
307         if (mCutoutSpace == null) {
308             updateLayoutParamsNoCutout();
309         }
310 
311         Rect bounds = new Rect();
312         boundsFromDirection(mDisplayCutout, Gravity.TOP, bounds);
313 
314         mCutoutSpace.setVisibility(View.VISIBLE);
315         RelativeLayout.LayoutParams lp = (LayoutParams) mCutoutSpace.getLayoutParams();
316         bounds.left = bounds.left + mCutoutSideNudge;
317         bounds.right = bounds.right - mCutoutSideNudge;
318         lp.width = bounds.width();
319         lp.height = bounds.height();
320         lp.addRule(RelativeLayout.CENTER_IN_PARENT);
321 
322         lp = (LayoutParams) mCarrierLabel.getLayoutParams();
323         lp.addRule(RelativeLayout.START_OF, R.id.cutout_space_view);
324 
325         lp = (LayoutParams) mStatusIconArea.getLayoutParams();
326         lp.addRule(RelativeLayout.RIGHT_OF, R.id.cutout_space_view);
327         lp.width = LayoutParams.MATCH_PARENT;
328 
329         LinearLayout.LayoutParams llp =
330                 (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
331         llp.setMarginStart(0);
332         return true;
333     }
334 
335     /** Should only be called from {@link KeyguardStatusBarViewController}. */
onUserInfoChanged(Drawable picture)336     void onUserInfoChanged(Drawable picture) {
337         mMultiUserAvatar.setImageDrawable(picture);
338     }
339 
340     /** Should only be called from {@link KeyguardStatusBarViewController}. */
onBatteryLevelChanged(boolean charging)341     void onBatteryLevelChanged(boolean charging) {
342         if (mBatteryCharging != charging) {
343             mBatteryCharging = charging;
344             updateVisibilities();
345         }
346     }
347 
setKeyguardUserSwitcherEnabled(boolean enabled)348     void setKeyguardUserSwitcherEnabled(boolean enabled) {
349         mKeyguardUserSwitcherEnabled = enabled;
350     }
351 
animateNextLayoutChange()352     private void animateNextLayoutChange() {
353         final int systemIconsCurrentX = mSystemIconsContainer.getLeft();
354         final boolean userAvatarVisible = mMultiUserAvatar.getParent() == mStatusIconArea;
355         getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
356             @Override
357             public boolean onPreDraw() {
358                 getViewTreeObserver().removeOnPreDrawListener(this);
359                 boolean userAvatarHiding = userAvatarVisible
360                         && mMultiUserAvatar.getParent() != mStatusIconArea;
361                 mSystemIconsContainer.setX(systemIconsCurrentX);
362                 mSystemIconsContainer.animate()
363                         .translationX(0)
364                         .setDuration(400)
365                         .setStartDelay(userAvatarHiding ? 300 : 0)
366                         .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
367                         .start();
368                 if (userAvatarHiding) {
369                     getOverlay().add(mMultiUserAvatar);
370                     mMultiUserAvatar.animate()
371                             .alpha(0f)
372                             .setDuration(300)
373                             .setStartDelay(0)
374                             .setInterpolator(Interpolators.ALPHA_OUT)
375                             .withEndAction(() -> {
376                                 mMultiUserAvatar.setAlpha(1f);
377                                 getOverlay().remove(mMultiUserAvatar);
378                             })
379                             .start();
380 
381                 } else {
382                     mMultiUserAvatar.setAlpha(0f);
383                     mMultiUserAvatar.animate()
384                             .alpha(1f)
385                             .setDuration(300)
386                             .setStartDelay(200)
387                             .setInterpolator(Interpolators.ALPHA_IN);
388                 }
389                 return true;
390             }
391         });
392 
393     }
394 
395     @Override
setVisibility(int visibility)396     public void setVisibility(int visibility) {
397         super.setVisibility(visibility);
398         if (visibility != View.VISIBLE) {
399             mSystemIconsContainer.animate().cancel();
400             mSystemIconsContainer.setTranslationX(0);
401             mMultiUserAvatar.animate().cancel();
402             mMultiUserAvatar.setAlpha(1f);
403         } else {
404             updateVisibilities();
405             updateSystemIconsLayoutParams();
406         }
407     }
408 
409     @Override
hasOverlappingRendering()410     public boolean hasOverlappingRendering() {
411         return false;
412     }
413 
414     /** Should only be called from {@link KeyguardStatusBarViewController}. */
onThemeChanged(StatusBarIconController.TintedIconManager iconManager)415     void onThemeChanged(StatusBarIconController.TintedIconManager iconManager) {
416         mBatteryView.setColorsFromContext(mContext);
417         updateIconsAndTextColors(iconManager);
418     }
419 
420     /** Should only be called from {@link KeyguardStatusBarViewController}. */
onOverlayChanged()421     void onOverlayChanged() {
422         mCarrierLabel.setTextAppearance(
423                 Utils.getThemeAttr(mContext, com.android.internal.R.attr.textAppearanceSmall));
424         mBatteryView.updatePercentView();
425     }
426 
updateIconsAndTextColors(StatusBarIconController.TintedIconManager iconManager)427     private void updateIconsAndTextColors(StatusBarIconController.TintedIconManager iconManager) {
428         @ColorInt int textColor = Utils.getColorAttrDefaultColor(mContext,
429                 R.attr.wallpaperTextColor);
430         @ColorInt int iconColor = Utils.getColorStateListDefaultColor(mContext,
431                 Color.luminance(textColor) < 0.5 ? R.color.dark_mode_icon_color_single_tone :
432                 R.color.light_mode_icon_color_single_tone);
433         float intensity = textColor == Color.WHITE ? 0 : 1;
434         mCarrierLabel.setTextColor(iconColor);
435         if (iconManager != null) {
436             iconManager.setTint(iconColor);
437         }
438 
439         applyDarkness(R.id.battery, mEmptyRect, intensity, iconColor);
440         applyDarkness(R.id.clock, mEmptyRect, intensity, iconColor);
441     }
442 
applyDarkness(int id, Rect tintArea, float intensity, int color)443     private void applyDarkness(int id, Rect tintArea, float intensity, int color) {
444         View v = findViewById(id);
445         if (v instanceof DarkReceiver) {
446             ((DarkReceiver) v).onDarkChanged(tintArea, intensity, color);
447         }
448     }
449 
450     /**
451      * Calculates the margin that isn't already accounted for in the view's padding.
452      */
calculateMargin(int margin, int padding)453     private int calculateMargin(int margin, int padding) {
454         if (padding >= margin) {
455             return 0;
456         } else {
457             return margin - padding;
458         }
459     }
460 
461     /** Should only be called from {@link KeyguardStatusBarViewController}. */
dump(FileDescriptor fd, PrintWriter pw, String[] args)462     void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
463         pw.println("KeyguardStatusBarView:");
464         pw.println("  mBatteryCharging: " + mBatteryCharging);
465         pw.println("  mLayoutState: " + mLayoutState);
466         pw.println("  mKeyguardUserSwitcherEnabled: " + mKeyguardUserSwitcherEnabled);
467         if (mBatteryView != null) {
468             mBatteryView.dump(fd, pw, args);
469         }
470     }
471 
onSystemChromeAnimationStart(boolean isAnimatingOut)472     void onSystemChromeAnimationStart(boolean isAnimatingOut) {
473         if (isAnimatingOut) {
474             mSystemIconsContainer.setVisibility(View.VISIBLE);
475             mSystemIconsContainer.setAlpha(0f);
476         }
477     }
478 
onSystemChromeAnimationEnd(boolean isAnimatingIn)479     void onSystemChromeAnimationEnd(boolean isAnimatingIn) {
480         // Make sure the system icons are out of the way
481         if (isAnimatingIn) {
482             mSystemIconsContainer.setVisibility(View.INVISIBLE);
483             mSystemIconsContainer.setAlpha(0f);
484         } else {
485             mSystemIconsContainer.setAlpha(1f);
486             mSystemIconsContainer.setVisibility(View.VISIBLE);
487         }
488     }
489 
onSystemChromeAnimationUpdate(float animatedValue)490     void onSystemChromeAnimationUpdate(float animatedValue) {
491         mSystemIconsContainer.setAlpha(animatedValue);
492     }
493 
494     @Override
onLayout(boolean changed, int l, int t, int r, int b)495     protected void onLayout(boolean changed, int l, int t, int r, int b) {
496         super.onLayout(changed, l, t, r, b);
497         updateClipping();
498     }
499 
500     /**
501      * Set the clipping on the top of the view.
502      *
503      * Should only be called from {@link KeyguardStatusBarViewController}.
504      */
setTopClipping(int topClipping)505     void setTopClipping(int topClipping) {
506         if (topClipping != mTopClipping) {
507             mTopClipping = topClipping;
508             updateClipping();
509         }
510     }
511 
updateClipping()512     private void updateClipping() {
513         mClipRect.set(0, mTopClipping, getWidth(), getHeight());
514         setClipBounds(mClipRect);
515     }
516 }
517