1/*
2 * Copyright (C) 2018 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
17package com.android.systemui.statusbar;
18
19import android.content.Context;
20import android.content.res.Configuration;
21import android.content.res.Resources;
22import android.graphics.Point;
23import android.graphics.Rect;
24import android.util.AttributeSet;
25import android.view.Display;
26import android.view.DisplayCutout;
27import android.view.View;
28import android.widget.TextView;
29
30import com.android.internal.annotations.VisibleForTesting;
31import com.android.keyguard.AlphaOptimizedLinearLayout;
32import com.android.systemui.R;
33import com.android.systemui.statusbar.policy.DarkIconDispatcher;
34
35import java.util.List;
36
37/**
38 * The view in the statusBar that contains part of the heads-up information
39 */
40public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout {
41    private int mAbsoluteStartPadding;
42    private int mEndMargin;
43    private View mIconPlaceholder;
44    private TextView mTextView;
45    private NotificationData.Entry mShowingEntry;
46    private Rect mLayoutedIconRect = new Rect();
47    private int[] mTmpPosition = new int[2];
48    private boolean mFirstLayout = true;
49    private boolean mPublicMode;
50    private int mMaxWidth;
51    private View mRootView;
52    private int mSysWinInset;
53    private int mCutOutInset;
54    private List<Rect> mCutOutBounds;
55    private Rect mIconDrawingRect = new Rect();
56    private Point mDisplaySize;
57    private Runnable mOnDrawingRectChangedListener;
58
59    public HeadsUpStatusBarView(Context context) {
60        this(context, null);
61    }
62
63    public HeadsUpStatusBarView(Context context, AttributeSet attrs) {
64        this(context, attrs, 0);
65    }
66
67    public HeadsUpStatusBarView(Context context, AttributeSet attrs, int defStyleAttr) {
68        this(context, attrs, defStyleAttr, 0);
69    }
70
71    public HeadsUpStatusBarView(Context context, AttributeSet attrs, int defStyleAttr,
72            int defStyleRes) {
73        super(context, attrs, defStyleAttr, defStyleRes);
74        Resources res = getResources();
75        mAbsoluteStartPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings)
76            + res.getDimensionPixelSize(
77                    com.android.internal.R.dimen.notification_content_margin_start);
78        mEndMargin = res.getDimensionPixelSize(
79                com.android.internal.R.dimen.notification_content_margin_end);
80        setPaddingRelative(mAbsoluteStartPadding, 0, mEndMargin, 0);
81        updateMaxWidth();
82    }
83
84    private void updateMaxWidth() {
85        int maxWidth = getResources().getDimensionPixelSize(R.dimen.qs_panel_width);
86        if (maxWidth != mMaxWidth) {
87            // maxWidth doesn't work with fill_parent, let's manually make it at most as big as the
88            // notification panel
89            mMaxWidth = maxWidth;
90            requestLayout();
91        }
92    }
93
94    @Override
95    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
96        if (mMaxWidth > 0) {
97            int newSize = Math.min(MeasureSpec.getSize(widthMeasureSpec), mMaxWidth);
98            widthMeasureSpec = MeasureSpec.makeMeasureSpec(newSize,
99                    MeasureSpec.getMode(widthMeasureSpec));
100        }
101        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
102    }
103
104    @Override
105    protected void onConfigurationChanged(Configuration newConfig) {
106        super.onConfigurationChanged(newConfig);
107        updateMaxWidth();
108    }
109
110    @VisibleForTesting
111    public HeadsUpStatusBarView(Context context, View iconPlaceholder, TextView textView) {
112        this(context);
113        mIconPlaceholder = iconPlaceholder;
114        mTextView = textView;
115    }
116
117    @Override
118    protected void onFinishInflate() {
119        super.onFinishInflate();
120        mIconPlaceholder = findViewById(R.id.icon_placeholder);
121        mTextView = findViewById(R.id.text);
122    }
123
124    public void setEntry(NotificationData.Entry entry) {
125        if (entry != null) {
126            mShowingEntry = entry;
127            CharSequence text = entry.headsUpStatusBarText;
128            if (mPublicMode) {
129                text = entry.headsUpStatusBarTextPublic;
130            }
131            mTextView.setText(text);
132        } else {
133            mShowingEntry = null;
134        }
135    }
136
137    @Override
138    protected void onLayout(boolean changed, int l, int t, int r, int b) {
139        super.onLayout(changed, l, t, r, b);
140        mIconPlaceholder.getLocationOnScreen(mTmpPosition);
141        int left = (int) (mTmpPosition[0] - getTranslationX());
142        int top = mTmpPosition[1];
143        int right = left + mIconPlaceholder.getWidth();
144        int bottom = top + mIconPlaceholder.getHeight();
145        mLayoutedIconRect.set(left, top, right, bottom);
146        updateDrawingRect();
147        int targetPadding = mAbsoluteStartPadding + mSysWinInset + mCutOutInset;
148        boolean isRtl = isLayoutRtl();
149        int start = isRtl ? (mDisplaySize.x - right) : left;
150
151        if (start != targetPadding) {
152            if (mCutOutBounds != null) {
153                for (Rect cutOutRect : mCutOutBounds) {
154                    int cutOutStart = (isRtl)
155                            ? (mDisplaySize.x - cutOutRect.right) : cutOutRect.left;
156                    if (start > cutOutStart) {
157                        start -= cutOutRect.width();
158                        break;
159                    }
160                }
161            }
162
163            int newPadding = targetPadding - start + getPaddingStart();
164            setPaddingRelative(newPadding, 0, mEndMargin, 0);
165        }
166        if (mFirstLayout) {
167            // we need to do the padding calculation in the first frame, so the layout specified
168            // our visibility to be INVISIBLE in the beginning. let's correct that and set it
169            // to GONE.
170            setVisibility(GONE);
171            mFirstLayout = false;
172        }
173    }
174
175    /** In order to do UI alignment, this view will be notified by
176     * {@link com.android.systemui.statusbar.stack.NotificationStackScrollLayout}.
177     * After scroller laid out, the scroller will tell this view about scroller's getX()
178     * @param translationX how to translate the horizontal position
179     */
180    public void setPanelTranslation(float translationX) {
181        if (isLayoutRtl()) {
182            setTranslationX(translationX + mCutOutInset);
183        } else {
184            setTranslationX(translationX - mCutOutInset);
185        }
186        updateDrawingRect();
187    }
188
189    private void updateDrawingRect() {
190        float oldLeft = mIconDrawingRect.left;
191        mIconDrawingRect.set(mLayoutedIconRect);
192        mIconDrawingRect.offset((int) getTranslationX(), 0);
193        if (oldLeft != mIconDrawingRect.left && mOnDrawingRectChangedListener != null) {
194            mOnDrawingRectChangedListener.run();
195        }
196    }
197
198    @Override
199    protected boolean fitSystemWindows(Rect insets) {
200        boolean isRtl = isLayoutRtl();
201        mSysWinInset = isRtl ? insets.right : insets.left;
202        DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
203        mCutOutInset = (displayCutout != null)
204                ? (isRtl ? displayCutout.getSafeInsetRight() : displayCutout.getSafeInsetLeft())
205                : 0;
206
207        getDisplaySize();
208
209        mCutOutBounds = null;
210        if (displayCutout != null && displayCutout.getSafeInsetRight() == 0
211                && displayCutout.getSafeInsetLeft() == 0) {
212            mCutOutBounds = displayCutout.getBoundingRects();
213        }
214
215        // For Double Cut Out mode, the System window navigation bar is at the right
216        // side of the left cut out. In this condition, mSysWinInset include the left cut
217        // out width so we set mCutOutInset to be 0. For RTL, the condition is the same.
218        // The navigation bar is at the left side of the right cut out and include the
219        // right cut out width.
220        if (mSysWinInset != 0) {
221            mCutOutInset = 0;
222        }
223
224        return super.fitSystemWindows(insets);
225    }
226
227    public NotificationData.Entry getShowingEntry() {
228        return mShowingEntry;
229    }
230
231    public Rect getIconDrawingRect() {
232        return mIconDrawingRect;
233    }
234
235    public void onDarkChanged(Rect area, float darkIntensity, int tint) {
236        mTextView.setTextColor(DarkIconDispatcher.getTint(area, this, tint));
237    }
238
239    public void setPublicMode(boolean publicMode) {
240        mPublicMode = publicMode;
241    }
242
243    public void setOnDrawingRectChangedListener(Runnable onDrawingRectChangedListener) {
244        mOnDrawingRectChangedListener = onDrawingRectChangedListener;
245    }
246
247    private void getDisplaySize() {
248        if (mDisplaySize == null) {
249            mDisplaySize = new Point();
250        }
251        getDisplay().getRealSize(mDisplaySize);
252    }
253
254    @Override
255    protected void onAttachedToWindow() {
256        super.onAttachedToWindow();
257        getDisplaySize();
258    }
259}
260