blob: 43ebe0493751f563a43695d9e8ba837117cde17c [file] [log] [blame]
Jorim Jaggi8bc983e2014-12-10 17:45:50 +01001/*
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
17package com.android.systemui.qs;
18
Jason Monke1be3422016-02-09 10:51:40 -050019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
Jorim Jaggi8bc983e2014-12-10 17:45:50 +010021import android.content.Context;
Jason Monk8b9d67f2016-03-02 08:59:08 -050022import android.graphics.Point;
Jorim Jaggi8bc983e2014-12-10 17:45:50 +010023import android.util.AttributeSet;
Jason Monke1be3422016-02-09 10:51:40 -050024import android.util.Log;
25import android.view.View;
26import android.view.ViewTreeObserver;
Jorim Jaggi8bc983e2014-12-10 17:45:50 +010027import android.widget.FrameLayout;
Jason Monke1be3422016-02-09 10:51:40 -050028import com.android.systemui.Interpolators;
Jorim Jaggi8bc983e2014-12-10 17:45:50 +010029import com.android.systemui.R;
Jason Monk8b9d67f2016-03-02 08:59:08 -050030import com.android.systemui.qs.customize.QSCustomizer;
Jason Monke1be3422016-02-09 10:51:40 -050031import com.android.systemui.statusbar.phone.BaseStatusBarHeader;
Jason Monk8b9d67f2016-03-02 08:59:08 -050032import com.android.systemui.statusbar.phone.NotificationPanelView;
Jason Monk162011e2016-02-19 08:11:55 -050033import com.android.systemui.statusbar.phone.QSTileHost;
Jason Monke1be3422016-02-09 10:51:40 -050034import com.android.systemui.statusbar.stack.StackStateAnimator;
Jorim Jaggi8bc983e2014-12-10 17:45:50 +010035
36/**
Jason Monke1be3422016-02-09 10:51:40 -050037 * Wrapper view with background which contains {@link QSPanel} and {@link BaseStatusBarHeader}
38 *
39 * Also manages animations for the QS Header and Panel.
Jorim Jaggi8bc983e2014-12-10 17:45:50 +010040 */
41public class QSContainer extends FrameLayout {
Jason Monke1be3422016-02-09 10:51:40 -050042 private static final String TAG = "QSContainer";
43 private static final boolean DEBUG = false;
Jorim Jaggi8bc983e2014-12-10 17:45:50 +010044
Jason Monk8b9d67f2016-03-02 08:59:08 -050045 private final Point mSizePoint = new Point();
46
Jorim Jaggi8bc983e2014-12-10 17:45:50 +010047 private int mHeightOverride = -1;
48 private QSPanel mQSPanel;
Jason Monk377e7ad2016-02-16 14:03:21 -050049 private QSDetail mQSDetail;
Jason Monke1be3422016-02-09 10:51:40 -050050 protected BaseStatusBarHeader mHeader;
51 private float mQsExpansion;
52 private boolean mQsExpanded;
53 private boolean mHeaderAnimating;
54 private boolean mKeyguardShowing;
55 private boolean mStackScrollerOverscrolling;
56
57 private long mDelay;
Jason Monk162011e2016-02-19 08:11:55 -050058 private QSAnimator mQSAnimator;
Jason Monk8b9d67f2016-03-02 08:59:08 -050059 private QSCustomizer mQSCustomizer;
60 private NotificationPanelView mPanelView;
Jorim Jaggi8bc983e2014-12-10 17:45:50 +010061
62 public QSContainer(Context context, AttributeSet attrs) {
63 super(context, attrs);
64 }
65
66 @Override
67 protected void onFinishInflate() {
68 super.onFinishInflate();
69 mQSPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
Jason Monk377e7ad2016-02-16 14:03:21 -050070 mQSDetail = (QSDetail) findViewById(R.id.qs_detail);
71 mQSDetail.setQsPanel(mQSPanel);
Jason Monke1be3422016-02-09 10:51:40 -050072 mHeader = (BaseStatusBarHeader) findViewById(R.id.header);
Jason Monk162011e2016-02-19 08:11:55 -050073 mQSAnimator = new QSAnimator(this, (QuickQSPanel) mHeader.findViewById(R.id.quick_qs_panel),
74 mQSPanel);
Jason Monk8b9d67f2016-03-02 08:59:08 -050075 mQSCustomizer = (QSCustomizer) findViewById(R.id.qs_customize);
76 mQSCustomizer.setQsContainer(this);
Jason Monk162011e2016-02-19 08:11:55 -050077 }
78
79 public void setHost(QSTileHost qsh) {
Jason Monk8b9d67f2016-03-02 08:59:08 -050080 mQSPanel.setHost(qsh, mQSCustomizer);
Jason Monk162011e2016-02-19 08:11:55 -050081 mHeader.setQSPanel(mQSPanel);
82 mQSDetail.setHost(qsh);
83 mQSAnimator.setHost(qsh);
Jorim Jaggi8bc983e2014-12-10 17:45:50 +010084 }
85
Jason Monk8b9d67f2016-03-02 08:59:08 -050086 public void setPanelView(NotificationPanelView panelView) {
87 mPanelView = panelView;
88 }
89
Jorim Jaggi8bc983e2014-12-10 17:45:50 +010090 @Override
Jason Monk83e431f2016-02-19 19:32:13 -050091 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
92 // Since we control our own bottom, be whatever size we want.
93 // Otherwise the QSPanel ends up with 0 height when the window is only the
94 // size of the status bar.
95 super.onMeasure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
Jason Monk8b9d67f2016-03-02 08:59:08 -050096
97 // QSCustomizer is always be the height of the screen, but do this after
98 // other measuring to avoid changing the height of the QSContainer.
99 getDisplay().getRealSize(mSizePoint);
100 mQSCustomizer.measure(widthMeasureSpec,
101 MeasureSpec.makeMeasureSpec(mSizePoint.y, MeasureSpec.EXACTLY));
Jason Monk83e431f2016-02-19 19:32:13 -0500102 }
103
104 @Override
Jorim Jaggi8bc983e2014-12-10 17:45:50 +0100105 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
106 super.onLayout(changed, left, top, right, bottom);
107 updateBottom();
108 }
109
Jason Monk8b9d67f2016-03-02 08:59:08 -0500110 public boolean isCustomizing() {
111 return mQSCustomizer.isCustomizing();
112 }
113
Jorim Jaggi8bc983e2014-12-10 17:45:50 +0100114 /**
115 * Overrides the height of this view (post-layout), so that the content is clipped to that
116 * height and the background is set to that height.
117 *
118 * @param heightOverride the overridden height
119 */
120 public void setHeightOverride(int heightOverride) {
121 mHeightOverride = heightOverride;
122 updateBottom();
123 }
124
125 /**
126 * The height this view wants to be. This is different from {@link #getMeasuredHeight} such that
127 * during closing the detail panel, this already returns the smaller height.
128 */
129 public int getDesiredHeight() {
Jason Monk8b9d67f2016-03-02 08:59:08 -0500130 if (isCustomizing()) {
131 return getHeight();
132 }
Jason Monk377e7ad2016-02-16 14:03:21 -0500133 if (mQSDetail.isClosingDetail()) {
Jason Monke1be3422016-02-09 10:51:40 -0500134 return mQSPanel.getGridHeight() + mHeader.getCollapsedHeight() + getPaddingBottom();
Jorim Jaggi8bc983e2014-12-10 17:45:50 +0100135 } else {
136 return getMeasuredHeight();
137 }
138 }
139
Jason Monk8b9d67f2016-03-02 08:59:08 -0500140 public void notifyCustomizeChanged() {
141 // The customize state changed, so our height changed.
142 updateBottom();
Jason Monk39c98e62016-03-16 09:18:35 -0400143 mQSPanel.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
144 mHeader.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
Jason Monk8b9d67f2016-03-02 08:59:08 -0500145 // Let the panel know the position changed and it needs to update where notifications
146 // and whatnot are.
147 mPanelView.onQsHeightChanged();
148 }
149
Jorim Jaggi8bc983e2014-12-10 17:45:50 +0100150 private void updateBottom() {
Jason Monke1be3422016-02-09 10:51:40 -0500151 int heightOverride = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight();
Jason Monk8b9d67f2016-03-02 08:59:08 -0500152 int height = mQSCustomizer.isCustomizing() ? mQSCustomizer.getHeight()
153 : (int) (mQsExpansion * (heightOverride - mHeader.getCollapsedHeight()))
Jason Monke1be3422016-02-09 10:51:40 -0500154 + mHeader.getCollapsedHeight();
Jorim Jaggi8bc983e2014-12-10 17:45:50 +0100155 setBottom(getTop() + height);
Jason Monk377e7ad2016-02-16 14:03:21 -0500156 mQSDetail.setBottom(getTop() + height);
Jorim Jaggi8bc983e2014-12-10 17:45:50 +0100157 }
Jason Monke1be3422016-02-09 10:51:40 -0500158
159 private void updateQsState() {
160 boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling || mHeaderAnimating;
161 mQSPanel.setExpanded(mQsExpanded);
162 mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
163 ? View.VISIBLE
164 : View.INVISIBLE);
165 mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating)
166 || (mQsExpanded && !mStackScrollerOverscrolling));
167 mQSPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
Jason Monke1be3422016-02-09 10:51:40 -0500168 }
169
170 public BaseStatusBarHeader getHeader() {
171 return mHeader;
172 }
173
174 public QSPanel getQsPanel() {
175 return mQSPanel;
176 }
177
Jason Monk8b9d67f2016-03-02 08:59:08 -0500178 public QSCustomizer getCustomizer() {
179 return mQSCustomizer;
180 }
181
Jason Monk377e7ad2016-02-16 14:03:21 -0500182 public boolean isShowingDetail() {
183 return mQSPanel.isShowingCustomize() || mQSDetail.isShowingDetail();
184 }
185
Jason Monke1be3422016-02-09 10:51:40 -0500186 public void setHeaderClickable(boolean clickable) {
187 if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
188 mHeader.setClickable(clickable);
189 }
190
191 public void setExpanded(boolean expanded) {
192 if (DEBUG) Log.d(TAG, "setExpanded " + expanded);
193 mQsExpanded = expanded;
194 updateQsState();
195 }
196
197 public void setKeyguardShowing(boolean keyguardShowing) {
198 if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
199 mKeyguardShowing = keyguardShowing;
Jason Monk8fb77872016-03-03 16:39:42 -0500200 mQSAnimator.setOnKeyguard(keyguardShowing);
Jason Monke1be3422016-02-09 10:51:40 -0500201 updateQsState();
202 }
203
204 public void setOverscrolling(boolean stackScrollerOverscrolling) {
205 if (DEBUG) Log.d(TAG, "setOverscrolling " + stackScrollerOverscrolling);
206 mStackScrollerOverscrolling = stackScrollerOverscrolling;
207 updateQsState();
208 }
209
210 public void setListening(boolean listening) {
211 if (DEBUG) Log.d(TAG, "setListening " + listening);
212 mQSPanel.setListening(listening);
213 mHeader.setListening(listening);
214 }
215
216 public void setQsExpansion(float expansion, float headerTranslation) {
217 if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation);
218 mQsExpansion = expansion;
219 final float translationScaleY = expansion - 1;
220 if (!mHeaderAnimating) {
221 setTranslationY(mKeyguardShowing ? (translationScaleY * mHeader.getHeight())
222 : headerTranslation);
223 }
224 mHeader.setExpansion(mKeyguardShowing ? 1 : expansion);
225 mQSPanel.setTranslationY(translationScaleY * mQSPanel.getHeight());
Jason Monk377e7ad2016-02-16 14:03:21 -0500226 mQSDetail.setFullyExpanded(expansion == 1);
Jason Monk162011e2016-02-19 08:11:55 -0500227 mQSAnimator.setPosition(expansion);
Jason Monke1be3422016-02-09 10:51:40 -0500228 updateBottom();
229 }
230
231 public void animateHeaderSlidingIn(long delay) {
232 if (DEBUG) Log.d(TAG, "animateHeaderSlidingIn");
233 // If the QS is already expanded we don't need to slide in the header as it's already
234 // visible.
235 if (!mQsExpanded) {
236 mHeaderAnimating = true;
237 mDelay = delay;
238 getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
239 }
240 }
241
242 public void animateHeaderSlidingOut() {
243 if (DEBUG) Log.d(TAG, "animateHeaderSlidingOut");
244 mHeaderAnimating = true;
245 animate().y(-mHeader.getHeight())
246 .setStartDelay(0)
247 .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
248 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
249 .setListener(new AnimatorListenerAdapter() {
250 @Override
251 public void onAnimationEnd(Animator animation) {
252 animate().setListener(null);
253 mHeaderAnimating = false;
254 updateQsState();
255 }
256 })
257 .start();
258 }
259
260 private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
261 = new ViewTreeObserver.OnPreDrawListener() {
262 @Override
263 public boolean onPreDraw() {
264 getViewTreeObserver().removeOnPreDrawListener(this);
265 animate()
266 .translationY(0f)
267 .setStartDelay(mDelay)
268 .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
269 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
270 .setListener(mAnimateHeaderSlidingInListener)
271 .start();
272 setY(-mHeader.getHeight());
273 return true;
274 }
275 };
276
277 private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
278 = new AnimatorListenerAdapter() {
279 @Override
280 public void onAnimationEnd(Animator animation) {
281 mHeaderAnimating = false;
282 updateQsState();
283 }
284 };
Jorim Jaggi8bc983e2014-12-10 17:45:50 +0100285}