/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package com.android.systemui.statusbar;

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.View;
import android.widget.TextView;

import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.AlphaOptimizedLinearLayout;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.DarkIconDispatcher;

import java.util.List;

The view in the statusBar that contains part of the heads-up information
/** * The view in the statusBar that contains part of the heads-up information */
public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout { private static final String HEADS_UP_STATUS_BAR_VIEW_SUPER_PARCELABLE = "heads_up_status_bar_view_super_parcelable"; private static final String FIRST_LAYOUT = "first_layout"; private static final String PUBLIC_MODE = "public_mode"; private static final String VISIBILITY = "visibility"; private static final String ALPHA = "alpha"; private int mAbsoluteStartPadding; private int mEndMargin; private View mIconPlaceholder; private TextView mTextView; private NotificationData.Entry mShowingEntry; private Rect mLayoutedIconRect = new Rect(); private int[] mTmpPosition = new int[2]; private boolean mFirstLayout = true; private boolean mPublicMode; private int mMaxWidth; private View mRootView; private int mSysWinInset; private int mCutOutInset; private List<Rect> mCutOutBounds; private Rect mIconDrawingRect = new Rect(); private Point mDisplaySize; private Runnable mOnDrawingRectChangedListener; public HeadsUpStatusBarView(Context context) { this(context, null); } public HeadsUpStatusBarView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public HeadsUpStatusBarView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public HeadsUpStatusBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); Resources res = getResources(); mAbsoluteStartPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings) + res.getDimensionPixelSize( com.android.internal.R.dimen.notification_content_margin_start); mEndMargin = res.getDimensionPixelSize( com.android.internal.R.dimen.notification_content_margin_end); setPaddingRelative(mAbsoluteStartPadding, 0, mEndMargin, 0); updateMaxWidth(); } private void updateMaxWidth() { int maxWidth = getResources().getDimensionPixelSize(R.dimen.qs_panel_width); if (maxWidth != mMaxWidth) { // maxWidth doesn't work with fill_parent, let's manually make it at most as big as the // notification panel mMaxWidth = maxWidth; requestLayout(); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mMaxWidth > 0) { int newSize = Math.min(MeasureSpec.getSize(widthMeasureSpec), mMaxWidth); widthMeasureSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.getMode(widthMeasureSpec)); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); updateMaxWidth(); } @Override public Bundle onSaveInstanceState() { Bundle bundle = new Bundle(); bundle.putParcelable(HEADS_UP_STATUS_BAR_VIEW_SUPER_PARCELABLE, super.onSaveInstanceState()); bundle.putBoolean(FIRST_LAYOUT, mFirstLayout); bundle.putBoolean(PUBLIC_MODE, mPublicMode); bundle.putInt(VISIBILITY, getVisibility()); bundle.putFloat(ALPHA, getAlpha()); return bundle; } @Override public void onRestoreInstanceState(Parcelable state) { if (state == null || !(state instanceof Bundle)) { super.onRestoreInstanceState(state); return; } Bundle bundle = (Bundle) state; Parcelable superState = bundle.getParcelable(HEADS_UP_STATUS_BAR_VIEW_SUPER_PARCELABLE); super.onRestoreInstanceState(superState); mFirstLayout = bundle.getBoolean(FIRST_LAYOUT, true); mPublicMode = bundle.getBoolean(PUBLIC_MODE, false); if (bundle.containsKey(VISIBILITY)) { setVisibility(bundle.getInt(VISIBILITY)); } if (bundle.containsKey(ALPHA)) { setAlpha(bundle.getFloat(ALPHA)); } } @VisibleForTesting public HeadsUpStatusBarView(Context context, View iconPlaceholder, TextView textView) { this(context); mIconPlaceholder = iconPlaceholder; mTextView = textView; } @Override protected void onFinishInflate() { super.onFinishInflate(); mIconPlaceholder = findViewById(R.id.icon_placeholder); mTextView = findViewById(R.id.text); } public void setEntry(NotificationData.Entry entry) { if (entry != null) { mShowingEntry = entry; CharSequence text = entry.headsUpStatusBarText; if (mPublicMode) { text = entry.headsUpStatusBarTextPublic; } mTextView.setText(text); } else { mShowingEntry = null; } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); mIconPlaceholder.getLocationOnScreen(mTmpPosition); int left = (int) (mTmpPosition[0] - getTranslationX()); int top = mTmpPosition[1]; int right = left + mIconPlaceholder.getWidth(); int bottom = top + mIconPlaceholder.getHeight(); mLayoutedIconRect.set(left, top, right, bottom); updateDrawingRect(); int targetPadding = mAbsoluteStartPadding + mSysWinInset + mCutOutInset; boolean isRtl = isLayoutRtl(); int start = isRtl ? (mDisplaySize.x - right) : left; if (start != targetPadding) { if (mCutOutBounds != null) { for (Rect cutOutRect : mCutOutBounds) { int cutOutStart = (isRtl) ? (mDisplaySize.x - cutOutRect.right) : cutOutRect.left; if (start > cutOutStart) { start -= cutOutRect.width(); break; } } } int newPadding = targetPadding - start + getPaddingStart(); setPaddingRelative(newPadding, 0, mEndMargin, 0); } if (mFirstLayout) { // we need to do the padding calculation in the first frame, so the layout specified // our visibility to be INVISIBLE in the beginning. let's correct that and set it // to GONE. setVisibility(GONE); mFirstLayout = false; } }
In order to do UI alignment, this view will be notified by NotificationStackScrollLayout. After scroller laid out, the scroller will tell this view about scroller's getX()
Params:
  • translationX – how to translate the horizontal position
/** In order to do UI alignment, this view will be notified by * {@link com.android.systemui.statusbar.stack.NotificationStackScrollLayout}. * After scroller laid out, the scroller will tell this view about scroller's getX() * @param translationX how to translate the horizontal position */
public void setPanelTranslation(float translationX) { setTranslationX(translationX); updateDrawingRect(); } private void updateDrawingRect() { float oldLeft = mIconDrawingRect.left; mIconDrawingRect.set(mLayoutedIconRect); mIconDrawingRect.offset((int) getTranslationX(), 0); if (oldLeft != mIconDrawingRect.left && mOnDrawingRectChangedListener != null) { mOnDrawingRectChangedListener.run(); } } @Override protected boolean fitSystemWindows(Rect insets) { boolean isRtl = isLayoutRtl(); mSysWinInset = isRtl ? insets.right : insets.left; DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout(); mCutOutInset = (displayCutout != null) ? (isRtl ? displayCutout.getSafeInsetRight() : displayCutout.getSafeInsetLeft()) : 0; getDisplaySize(); mCutOutBounds = null; if (displayCutout != null && displayCutout.getSafeInsetRight() == 0 && displayCutout.getSafeInsetLeft() == 0) { mCutOutBounds = displayCutout.getBoundingRects(); } // For Double Cut Out mode, the System window navigation bar is at the right // side of the left cut out. In this condition, mSysWinInset include the left cut // out width so we set mCutOutInset to be 0. For RTL, the condition is the same. // The navigation bar is at the left side of the right cut out and include the // right cut out width. if (mSysWinInset != 0) { mCutOutInset = 0; } return super.fitSystemWindows(insets); } public NotificationData.Entry getShowingEntry() { return mShowingEntry; } public Rect getIconDrawingRect() { return mIconDrawingRect; } public void onDarkChanged(Rect area, float darkIntensity, int tint) { mTextView.setTextColor(DarkIconDispatcher.getTint(area, this, tint)); } public void setPublicMode(boolean publicMode) { mPublicMode = publicMode; } public void setOnDrawingRectChangedListener(Runnable onDrawingRectChangedListener) { mOnDrawingRectChangedListener = onDrawingRectChangedListener; } private void getDisplaySize() { if (mDisplaySize == null) { mDisplaySize = new Point(); } getDisplay().getRealSize(mDisplaySize); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); getDisplaySize(); } }