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