blob: 3f090f8cbbf17db49ab5ab834dce7c2396369877 [file] [log] [blame]
Jason Monk0ceef212016-11-02 14:05:23 -04001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14
15package com.android.systemui.qs;
16
17import android.animation.Animator;
18import android.animation.AnimatorListenerAdapter;
19import android.annotation.Nullable;
20import android.app.Fragment;
21import android.content.res.Configuration;
22import android.graphics.Rect;
23import android.os.Bundle;
Jason Monk64b214e2017-03-27 13:40:59 -040024import android.support.annotation.VisibleForTesting;
Jason Monk0ceef212016-11-02 14:05:23 -040025import android.util.Log;
Jason Monk9a376bc2017-05-10 09:52:10 -040026import android.view.ContextThemeWrapper;
Jason Monk0ceef212016-11-02 14:05:23 -040027import android.view.LayoutInflater;
28import android.view.View;
Jason Monke5b770e2017-03-03 21:49:29 -050029import android.view.View.OnClickListener;
Jason Monk0ceef212016-11-02 14:05:23 -040030import android.view.ViewGroup;
31import android.view.ViewTreeObserver;
32import android.widget.FrameLayout.LayoutParams;
33
34import com.android.systemui.Interpolators;
35import com.android.systemui.R;
Jason Monkb4cc7b12017-05-09 13:50:47 -040036import com.android.systemui.R.id;
Jason Monk9a376bc2017-05-10 09:52:10 -040037import com.android.systemui.R.style;
Jason Monk0ceef212016-11-02 14:05:23 -040038import com.android.systemui.plugins.qs.QS;
39import com.android.systemui.qs.customize.QSCustomizer;
40import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
Jason Monk0ceef212016-11-02 14:05:23 -040041import com.android.systemui.statusbar.stack.StackStateAnimator;
42
43public class QSFragment extends Fragment implements QS {
44 private static final String TAG = "QS";
45 private static final boolean DEBUG = false;
Jason Monk64b214e2017-03-27 13:40:59 -040046 private static final String EXTRA_EXPANDED = "expanded";
47 private static final String EXTRA_LISTENING = "listening";
Jason Monk0ceef212016-11-02 14:05:23 -040048
49 private final Rect mQsBounds = new Rect();
50 private boolean mQsExpanded;
51 private boolean mHeaderAnimating;
52 private boolean mKeyguardShowing;
53 private boolean mStackScrollerOverscrolling;
54
55 private long mDelay;
56
57 private QSAnimator mQSAnimator;
58 private HeightListener mPanelView;
59 protected QuickStatusBarHeader mHeader;
60 private QSCustomizer mQSCustomizer;
61 protected QSPanel mQSPanel;
62 private QSDetail mQSDetail;
63 private boolean mListening;
64 private QSContainerImpl mContainer;
65 private int mLayoutDirection;
Jason Monke5b770e2017-03-03 21:49:29 -050066 private QSFooter mFooter;
Jason Monkb4cc7b12017-05-09 13:50:47 -040067 private int mGutterHeight;
Jason Monk0ceef212016-11-02 14:05:23 -040068
69 @Override
70 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
71 Bundle savedInstanceState) {
Jason Monk9a376bc2017-05-10 09:52:10 -040072 inflater =inflater.cloneInContext(new ContextThemeWrapper(getContext(), R.style.qs_theme));
Jason Monk0ceef212016-11-02 14:05:23 -040073 return inflater.inflate(R.layout.qs_panel, container, false);
74 }
75
76 @Override
77 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
78 super.onViewCreated(view, savedInstanceState);
Jason Monke5b770e2017-03-03 21:49:29 -050079 mQSPanel = view.findViewById(R.id.quick_settings_panel);
80 mQSDetail = view.findViewById(R.id.qs_detail);
81 mHeader = view.findViewById(R.id.header);
82 mFooter = view.findViewById(R.id.qs_footer);
Jason Monkb4cc7b12017-05-09 13:50:47 -040083 mContainer = view.findViewById(id.quick_settings_container);
84 mGutterHeight = getContext().getResources().getDimensionPixelSize(R.dimen.qs_gutter_height);
Jason Monk0ceef212016-11-02 14:05:23 -040085
86 mQSDetail.setQsPanel(mQSPanel, mHeader);
Anthony Chen3cb3ad92016-12-01 10:58:47 -080087
88 // If the quick settings row is not shown, then there is no need for the animation from
89 // the row to the full QS panel.
90 if (getResources().getBoolean(R.bool.config_showQuickSettingsRow)) {
91 mQSAnimator = new QSAnimator(this,
Jason Monke5b770e2017-03-03 21:49:29 -050092 mHeader.findViewById(R.id.quick_qs_panel), mQSPanel);
Anthony Chen3cb3ad92016-12-01 10:58:47 -080093 }
94
Jason Monke5b770e2017-03-03 21:49:29 -050095 mQSCustomizer = view.findViewById(R.id.qs_customize);
Jason Monk0ceef212016-11-02 14:05:23 -040096 mQSCustomizer.setQs(this);
Jason Monk64b214e2017-03-27 13:40:59 -040097 if (savedInstanceState != null) {
98 setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED));
99 setListening(savedInstanceState.getBoolean(EXTRA_LISTENING));
100 }
101 }
102
103 @Override
104 public void onDestroy() {
105 super.onDestroy();
106 if (mListening) {
107 setListening(false);
108 }
109 }
110
111 @Override
112 public void onSaveInstanceState(Bundle outState) {
113 super.onSaveInstanceState(outState);
114 outState.putBoolean(EXTRA_EXPANDED, mQsExpanded);
115 outState.putBoolean(EXTRA_LISTENING, mListening);
116 }
117
118 @VisibleForTesting
119 boolean isListening() {
120 return mListening;
121 }
122
123 @VisibleForTesting
124 boolean isExpanded() {
125 return mQsExpanded;
Jason Monk0ceef212016-11-02 14:05:23 -0400126 }
127
Jason Monke5b770e2017-03-03 21:49:29 -0500128 @Override
129 public View getHeader() {
130 return mHeader;
131 }
132
Jason Monk0ceef212016-11-02 14:05:23 -0400133 public void setPanelView(HeightListener panelView) {
134 mPanelView = panelView;
135 }
136
137 @Override
138 public void onConfigurationChanged(Configuration newConfig) {
139 super.onConfigurationChanged(newConfig);
140 if (newConfig.getLayoutDirection() != mLayoutDirection) {
141 mLayoutDirection = newConfig.getLayoutDirection();
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800142
143 if (mQSAnimator != null) {
144 mQSAnimator.onRtlChanged();
145 }
Jason Monk0ceef212016-11-02 14:05:23 -0400146 }
147 }
148
149 @Override
150 public void setContainer(ViewGroup container) {
151 if (container instanceof NotificationsQuickSettingsContainer) {
152 mQSCustomizer.setContainer((NotificationsQuickSettingsContainer) container);
153 }
154 }
155
156 public boolean isCustomizing() {
157 return mQSCustomizer.isCustomizing();
158 }
159
160 public void setHost(QSTileHost qsh) {
161 mQSPanel.setHost(qsh, mQSCustomizer);
162 mHeader.setQSPanel(mQSPanel);
Jason Monke5b770e2017-03-03 21:49:29 -0500163 mFooter.setQSPanel(mQSPanel);
Jason Monk0ceef212016-11-02 14:05:23 -0400164 mQSDetail.setHost(qsh);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800165
166 if (mQSAnimator != null) {
167 mQSAnimator.setHost(qsh);
168 }
Jason Monk0ceef212016-11-02 14:05:23 -0400169 }
170
171 private void updateQsState() {
172 final boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling
173 || mHeaderAnimating;
174 mQSPanel.setExpanded(mQsExpanded);
175 mQSDetail.setExpanded(mQsExpanded);
176 mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
177 ? View.VISIBLE
178 : View.INVISIBLE);
179 mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating)
180 || (mQsExpanded && !mStackScrollerOverscrolling));
Jason Monke5b770e2017-03-03 21:49:29 -0500181 mFooter.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
182 ? View.VISIBLE
183 : View.INVISIBLE);
184 mFooter.setExpanded((mKeyguardShowing && !mHeaderAnimating)
185 || (mQsExpanded && !mStackScrollerOverscrolling));
Jason Monk0ceef212016-11-02 14:05:23 -0400186 mQSPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
187 }
188
Jason Monk0ceef212016-11-02 14:05:23 -0400189 public QSPanel getQsPanel() {
190 return mQSPanel;
191 }
192
193 public QSCustomizer getCustomizer() {
194 return mQSCustomizer;
195 }
196
197 public boolean isShowingDetail() {
198 return mQSPanel.isShowingCustomize() || mQSDetail.isShowingDetail();
199 }
200
201 public void setHeaderClickable(boolean clickable) {
202 if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
Jason Monk1fdde2d2017-03-08 09:39:21 -0500203 mFooter.getExpandView().setClickable(clickable);
Jason Monk0ceef212016-11-02 14:05:23 -0400204 }
205
206 public void setExpanded(boolean expanded) {
207 if (DEBUG) Log.d(TAG, "setExpanded " + expanded);
208 mQsExpanded = expanded;
209 mQSPanel.setListening(mListening && mQsExpanded);
210 updateQsState();
211 }
212
213 public void setKeyguardShowing(boolean keyguardShowing) {
214 if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
215 mKeyguardShowing = keyguardShowing;
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800216
217 if (mQSAnimator != null) {
218 mQSAnimator.setOnKeyguard(keyguardShowing);
219 }
220
Jason Monk0ceef212016-11-02 14:05:23 -0400221 updateQsState();
222 }
223
224 public void setOverscrolling(boolean stackScrollerOverscrolling) {
225 if (DEBUG) Log.d(TAG, "setOverscrolling " + stackScrollerOverscrolling);
226 mStackScrollerOverscrolling = stackScrollerOverscrolling;
227 updateQsState();
228 }
229
230 public void setListening(boolean listening) {
231 if (DEBUG) Log.d(TAG, "setListening " + listening);
232 mListening = listening;
233 mHeader.setListening(listening);
Jason Monke5b770e2017-03-03 21:49:29 -0500234 mFooter.setListening(listening);
Jason Monk0ceef212016-11-02 14:05:23 -0400235 mQSPanel.setListening(mListening && mQsExpanded);
236 }
237
238 public void setHeaderListening(boolean listening) {
239 mHeader.setListening(listening);
Jason Monke5b770e2017-03-03 21:49:29 -0500240 mFooter.setListening(listening);
Jason Monk0ceef212016-11-02 14:05:23 -0400241 }
242
243 public void setQsExpansion(float expansion, float headerTranslation) {
244 if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation);
245 mContainer.setExpansion(expansion);
246 final float translationScaleY = expansion - 1;
247 if (!mHeaderAnimating) {
Jason Monkb4cc7b12017-05-09 13:50:47 -0400248 int height = mHeader.getHeight() + mGutterHeight;
249 getView().setTranslationY(mKeyguardShowing ? (translationScaleY * height)
Jason Monk0ceef212016-11-02 14:05:23 -0400250 : headerTranslation);
251 }
252 mHeader.setExpansion(mKeyguardShowing ? 1 : expansion);
Jason Monke5b770e2017-03-03 21:49:29 -0500253 mFooter.setExpansion(mKeyguardShowing ? 1 : expansion);
254 int heightDiff = mQSPanel.getBottom() - mHeader.getBottom() + mHeader.getPaddingBottom();
255 mQSPanel.setTranslationY(translationScaleY * heightDiff);
Jason Monk0ceef212016-11-02 14:05:23 -0400256 mQSDetail.setFullyExpanded(expansion == 1);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800257
258 if (mQSAnimator != null) {
259 mQSAnimator.setPosition(expansion);
260 }
Jason Monk0ceef212016-11-02 14:05:23 -0400261
262 // Set bounds on the QS panel so it doesn't run over the header.
Jason Monkce4be852017-03-27 17:09:30 -0400263 mQsBounds.top = (int) (mQSPanel.getHeight() * (1 - expansion));
Jason Monk0ceef212016-11-02 14:05:23 -0400264 mQsBounds.right = mQSPanel.getWidth();
265 mQsBounds.bottom = mQSPanel.getHeight();
266 mQSPanel.setClipBounds(mQsBounds);
267 }
268
269 public void animateHeaderSlidingIn(long delay) {
270 if (DEBUG) Log.d(TAG, "animateHeaderSlidingIn");
271 // If the QS is already expanded we don't need to slide in the header as it's already
272 // visible.
273 if (!mQsExpanded) {
274 mHeaderAnimating = true;
275 mDelay = delay;
276 getView().getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
277 }
278 }
279
280 public void animateHeaderSlidingOut() {
281 if (DEBUG) Log.d(TAG, "animateHeaderSlidingOut");
282 mHeaderAnimating = true;
283 getView().animate().y(-mHeader.getHeight())
284 .setStartDelay(0)
285 .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
286 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
287 .setListener(new AnimatorListenerAdapter() {
288 @Override
289 public void onAnimationEnd(Animator animation) {
290 getView().animate().setListener(null);
291 mHeaderAnimating = false;
292 updateQsState();
293 }
294 })
295 .start();
296 }
297
298 @Override
Jason Monke5b770e2017-03-03 21:49:29 -0500299 public void setExpandClickListener(OnClickListener onClickListener) {
300 mFooter.getExpandView().setOnClickListener(onClickListener);
301 }
302
303 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400304 public void closeDetail() {
305 mQSPanel.closeDetail();
306 }
307
308 public void notifyCustomizeChanged() {
309 // The customize state changed, so our height changed.
310 mContainer.updateBottom();
311 mQSPanel.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
312 mHeader.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
Jason Monke5b770e2017-03-03 21:49:29 -0500313 mFooter.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
Jason Monk0ceef212016-11-02 14:05:23 -0400314 // Let the panel know the position changed and it needs to update where notifications
315 // and whatnot are.
316 mPanelView.onQsHeightChanged();
317 }
318
319 /**
320 * The height this view wants to be. This is different from {@link #getMeasuredHeight} such that
321 * during closing the detail panel, this already returns the smaller height.
322 */
323 public int getDesiredHeight() {
324 if (mQSCustomizer.isCustomizing()) {
325 return getView().getHeight();
326 }
327 if (mQSDetail.isClosingDetail()) {
Jason Monke5b770e2017-03-03 21:49:29 -0500328 LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams();
329 int panelHeight = layoutParams.topMargin + layoutParams.bottomMargin +
Jason Monk0ceef212016-11-02 14:05:23 -0400330 + mQSPanel.getMeasuredHeight();
Jason Monkb4cc7b12017-05-09 13:50:47 -0400331 return panelHeight + getView().getPaddingBottom() + mGutterHeight;
Jason Monk0ceef212016-11-02 14:05:23 -0400332 } else {
Jason Monkb4cc7b12017-05-09 13:50:47 -0400333 return getView().getMeasuredHeight() + mGutterHeight;
Jason Monk0ceef212016-11-02 14:05:23 -0400334 }
335 }
336
337 @Override
338 public void setHeightOverride(int desiredHeight) {
Jason Monkb4cc7b12017-05-09 13:50:47 -0400339 mContainer.setHeightOverride(desiredHeight - mGutterHeight);
Jason Monk0ceef212016-11-02 14:05:23 -0400340 }
341
342 public int getQsMinExpansionHeight() {
Jason Monkb4cc7b12017-05-09 13:50:47 -0400343 return mHeader.getHeight() + mGutterHeight;
Jason Monk0ceef212016-11-02 14:05:23 -0400344 }
345
346 @Override
347 public void hideImmediately() {
348 getView().animate().cancel();
349 getView().setY(-mHeader.getHeight());
350 }
351
352 private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
353 = new ViewTreeObserver.OnPreDrawListener() {
354 @Override
355 public boolean onPreDraw() {
356 getView().getViewTreeObserver().removeOnPreDrawListener(this);
357 getView().animate()
358 .translationY(0f)
359 .setStartDelay(mDelay)
360 .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
361 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
362 .setListener(mAnimateHeaderSlidingInListener)
363 .start();
364 getView().setY(-mHeader.getHeight());
365 return true;
366 }
367 };
368
369 private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
370 = new AnimatorListenerAdapter() {
371 @Override
372 public void onAnimationEnd(Animator animation) {
373 mHeaderAnimating = false;
374 updateQsState();
375 }
376 };
377}