blob: 19a5d522f53043cfb9697780cd909e51a8cdea36 [file] [log] [blame]
/*
* Copyright (C) 2014 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.qs;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.statusbar.phone.BaseStatusBarHeader;
import com.android.systemui.statusbar.phone.NotificationPanelView;
import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.statusbar.stack.StackStateAnimator;
/**
* Wrapper view with background which contains {@link QSPanel} and {@link BaseStatusBarHeader}
*
* Also manages animations for the QS Header and Panel.
*/
public class QSContainer extends FrameLayout {
private static final String TAG = "QSContainer";
private static final boolean DEBUG = false;
private final Point mSizePoint = new Point();
private final Rect mQsBounds = new Rect();
private int mHeightOverride = -1;
protected QSPanel mQSPanel;
private QSDetail mQSDetail;
protected BaseStatusBarHeader mHeader;
protected float mQsExpansion;
private boolean mQsExpanded;
private boolean mHeaderAnimating;
private boolean mKeyguardShowing;
private boolean mStackScrollerOverscrolling;
private long mDelay;
private QSAnimator mQSAnimator;
private QSCustomizer mQSCustomizer;
private NotificationPanelView mPanelView;
private boolean mListening;
public QSContainer(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mQSPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
mQSDetail = (QSDetail) findViewById(R.id.qs_detail);
mHeader = (BaseStatusBarHeader) findViewById(R.id.header);
mQSDetail.setQsPanel(mQSPanel, mHeader);
mQSAnimator = new QSAnimator(this, (QuickQSPanel) mHeader.findViewById(R.id.quick_qs_panel),
mQSPanel);
mQSCustomizer = (QSCustomizer) findViewById(R.id.qs_customize);
mQSCustomizer.setQsContainer(this);
}
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
super.onRtlPropertiesChanged(layoutDirection);
mQSAnimator.onRtlChanged();
}
public void setHost(QSTileHost qsh) {
mQSPanel.setHost(qsh, mQSCustomizer);
mHeader.setQSPanel(mQSPanel);
mQSDetail.setHost(qsh);
mQSAnimator.setHost(qsh);
}
public void setPanelView(NotificationPanelView panelView) {
mPanelView = panelView;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Since we control our own bottom, be whatever size we want.
// Otherwise the QSPanel ends up with 0 height when the window is only the
// size of the status bar.
mQSPanel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(
MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED));
int width = mQSPanel.getMeasuredWidth();
int height = ((LayoutParams) mQSPanel.getLayoutParams()).topMargin
+ mQSPanel.getMeasuredHeight();
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
// QSCustomizer is always be the height of the screen, but do this after
// other measuring to avoid changing the height of the QSContainer.
getDisplay().getRealSize(mSizePoint);
mQSCustomizer.measure(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(mSizePoint.y, MeasureSpec.EXACTLY));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
updateBottom();
}
public boolean isCustomizing() {
return mQSCustomizer.isCustomizing();
}
/**
* Overrides the height of this view (post-layout), so that the content is clipped to that
* height and the background is set to that height.
*
* @param heightOverride the overridden height
*/
public void setHeightOverride(int heightOverride) {
mHeightOverride = heightOverride;
updateBottom();
}
/**
* The height this view wants to be. This is different from {@link #getMeasuredHeight} such that
* during closing the detail panel, this already returns the smaller height.
*/
public int getDesiredHeight() {
if (isCustomizing()) {
return getHeight();
}
if (mQSDetail.isClosingDetail()) {
int panelHeight = ((LayoutParams) mQSPanel.getLayoutParams()).topMargin
+ mQSPanel.getMeasuredHeight();
return panelHeight + getPaddingBottom();
} else {
return getMeasuredHeight();
}
}
public void notifyCustomizeChanged() {
// The customize state changed, so our height changed.
updateBottom();
mQSPanel.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
mHeader.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
// Let the panel know the position changed and it needs to update where notifications
// and whatnot are.
mPanelView.onQsHeightChanged();
}
private void updateBottom() {
int height = calculateContainerHeight();
setBottom(getTop() + height);
mQSDetail.setBottom(getTop() + height);
}
protected int calculateContainerHeight() {
int heightOverride = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight();
return mQSCustomizer.isCustomizing() ? mQSCustomizer.getHeight()
: (int) (mQsExpansion * (heightOverride - mHeader.getCollapsedHeight()))
+ mHeader.getCollapsedHeight();
}
private void updateQsState() {
boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling || mHeaderAnimating;
mQSPanel.setExpanded(mQsExpanded);
mQSDetail.setExpanded(mQsExpanded);
mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
? View.VISIBLE
: View.INVISIBLE);
mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating)
|| (mQsExpanded && !mStackScrollerOverscrolling));
mQSPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
}
public BaseStatusBarHeader getHeader() {
return mHeader;
}
public QSPanel getQsPanel() {
return mQSPanel;
}
public QSCustomizer getCustomizer() {
return mQSCustomizer;
}
public boolean isShowingDetail() {
return mQSPanel.isShowingCustomize() || mQSDetail.isShowingDetail();
}
public void setHeaderClickable(boolean clickable) {
if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
mHeader.setClickable(clickable);
}
public void setExpanded(boolean expanded) {
if (DEBUG) Log.d(TAG, "setExpanded " + expanded);
mQsExpanded = expanded;
mQSPanel.setListening(mListening && mQsExpanded);
updateQsState();
}
public void setKeyguardShowing(boolean keyguardShowing) {
if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
mKeyguardShowing = keyguardShowing;
mQSAnimator.setOnKeyguard(keyguardShowing);
updateQsState();
}
public void setOverscrolling(boolean stackScrollerOverscrolling) {
if (DEBUG) Log.d(TAG, "setOverscrolling " + stackScrollerOverscrolling);
mStackScrollerOverscrolling = stackScrollerOverscrolling;
updateQsState();
}
public void setListening(boolean listening) {
if (DEBUG) Log.d(TAG, "setListening " + listening);
mListening = listening;
mHeader.setListening(listening);
mQSPanel.setListening(mListening && mQsExpanded);
}
public void setHeaderListening(boolean listening) {
mHeader.setListening(listening);
}
public void setQsExpansion(float expansion, float headerTranslation) {
if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation);
mQsExpansion = expansion;
final float translationScaleY = expansion - 1;
if (!mHeaderAnimating) {
setTranslationY(mKeyguardShowing ? (translationScaleY * mHeader.getHeight())
: headerTranslation);
}
mHeader.setExpansion(mKeyguardShowing ? 1 : expansion);
mQSPanel.setTranslationY(translationScaleY * mQSPanel.getHeight());
mQSDetail.setFullyExpanded(expansion == 1);
mQSAnimator.setPosition(expansion);
updateBottom();
// Set bounds on the QS panel so it doesn't run over the header.
mQsBounds.top = (int) (mQSPanel.getHeight() * (1 - expansion));
mQsBounds.right = mQSPanel.getWidth();
mQsBounds.bottom = mQSPanel.getHeight();
mQSPanel.setClipBounds(mQsBounds);
}
public void animateHeaderSlidingIn(long delay) {
if (DEBUG) Log.d(TAG, "animateHeaderSlidingIn");
// If the QS is already expanded we don't need to slide in the header as it's already
// visible.
if (!mQsExpanded) {
mHeaderAnimating = true;
mDelay = delay;
getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
}
}
public void animateHeaderSlidingOut() {
if (DEBUG) Log.d(TAG, "animateHeaderSlidingOut");
mHeaderAnimating = true;
animate().y(-mHeader.getHeight())
.setStartDelay(0)
.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
animate().setListener(null);
mHeaderAnimating = false;
updateQsState();
}
})
.start();
}
private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
= new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
animate()
.translationY(0f)
.setStartDelay(mDelay)
.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.setListener(mAnimateHeaderSlidingInListener)
.start();
setY(-mHeader.getHeight());
return true;
}
};
private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
= new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mHeaderAnimating = false;
updateQsState();
}
};
public int getQsMinExpansionHeight() {
return mHeader.getHeight();
}
}