blob: cdf0c0f32d9b82fa98b6d2c0cd2fd19f4dbd7c57 [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;
Jason Monk0ceef212016-11-02 14:05:23 -040019import android.app.Fragment;
20import android.content.res.Configuration;
21import android.graphics.Rect;
22import android.os.Bundle;
Anthony Chen54daefe2017-04-07 17:19:54 -070023import android.support.annotation.Nullable;
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 Monk0ceef212016-11-02 14:05:23 -040037import com.android.systemui.plugins.qs.QS;
38import com.android.systemui.qs.customize.QSCustomizer;
39import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
Jason Monk0ceef212016-11-02 14:05:23 -040040import com.android.systemui.statusbar.stack.StackStateAnimator;
41
42public class QSFragment extends Fragment implements QS {
43 private static final String TAG = "QS";
44 private static final boolean DEBUG = false;
Jason Monk64b214e2017-03-27 13:40:59 -040045 private static final String EXTRA_EXPANDED = "expanded";
46 private static final String EXTRA_LISTENING = "listening";
Jason Monk0ceef212016-11-02 14:05:23 -040047
48 private final Rect mQsBounds = new Rect();
49 private boolean mQsExpanded;
50 private boolean mHeaderAnimating;
51 private boolean mKeyguardShowing;
52 private boolean mStackScrollerOverscrolling;
53
54 private long mDelay;
55
56 private QSAnimator mQSAnimator;
57 private HeightListener mPanelView;
58 protected QuickStatusBarHeader mHeader;
59 private QSCustomizer mQSCustomizer;
60 protected QSPanel mQSPanel;
61 private QSDetail mQSDetail;
62 private boolean mListening;
63 private QSContainerImpl mContainer;
64 private int mLayoutDirection;
Jason Monke5b770e2017-03-03 21:49:29 -050065 private QSFooter mFooter;
Selim Cinek1a891a92017-12-04 17:41:27 +010066 private float mLastQSExpansion = -1;
Jason Monk0ceef212016-11-02 14:05:23 -040067
68 @Override
69 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
70 Bundle savedInstanceState) {
Jason Monk9a376bc2017-05-10 09:52:10 -040071 inflater =inflater.cloneInContext(new ContextThemeWrapper(getContext(), R.style.qs_theme));
Jason Monk0ceef212016-11-02 14:05:23 -040072 return inflater.inflate(R.layout.qs_panel, container, false);
73 }
74
75 @Override
76 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
77 super.onViewCreated(view, savedInstanceState);
Jason Monke5b770e2017-03-03 21:49:29 -050078 mQSPanel = view.findViewById(R.id.quick_settings_panel);
79 mQSDetail = view.findViewById(R.id.qs_detail);
80 mHeader = view.findViewById(R.id.header);
81 mFooter = view.findViewById(R.id.qs_footer);
Jason Monkb4cc7b12017-05-09 13:50:47 -040082 mContainer = view.findViewById(id.quick_settings_container);
Jason Monk0ceef212016-11-02 14:05:23 -040083
Anthony Chen54daefe2017-04-07 17:19:54 -070084 mQSDetail.setQsPanel(mQSPanel, mHeader, (View) mFooter);
85 mQSAnimator = new QSAnimator(this,
86 mHeader.findViewById(R.id.quick_qs_panel), mQSPanel);
Anthony Chen3cb3ad92016-12-01 10:58:47 -080087
Jason Monke5b770e2017-03-03 21:49:29 -050088 mQSCustomizer = view.findViewById(R.id.qs_customize);
Jason Monk0ceef212016-11-02 14:05:23 -040089 mQSCustomizer.setQs(this);
Jason Monk64b214e2017-03-27 13:40:59 -040090 if (savedInstanceState != null) {
91 setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED));
92 setListening(savedInstanceState.getBoolean(EXTRA_LISTENING));
Selim Cinek707e2072017-06-30 18:32:40 +020093 int[] loc = new int[2];
94 View edit = view.findViewById(android.R.id.edit);
95 edit.getLocationInWindow(loc);
96 int x = loc[0] + edit.getWidth() / 2;
97 int y = loc[1] + edit.getHeight() / 2;
98 mQSCustomizer.setEditLocation(x, y);
99 mQSCustomizer.restoreInstanceState(savedInstanceState);
Jason Monk64b214e2017-03-27 13:40:59 -0400100 }
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);
Selim Cinek707e2072017-06-30 18:32:40 +0200116 mQSCustomizer.saveInstanceState(outState);
Jason Monk64b214e2017-03-27 13:40:59 -0400117 }
118
119 @VisibleForTesting
120 boolean isListening() {
121 return mListening;
122 }
123
124 @VisibleForTesting
125 boolean isExpanded() {
126 return mQsExpanded;
Jason Monk0ceef212016-11-02 14:05:23 -0400127 }
128
Jason Monke5b770e2017-03-03 21:49:29 -0500129 @Override
130 public View getHeader() {
131 return mHeader;
132 }
133
Jason Monk4de5d3c2017-05-24 14:57:10 -0400134 @Override
135 public void setHasNotifications(boolean hasNotifications) {
Jason Monk4de5d3c2017-05-24 14:57:10 -0400136 }
137
Anthony Chen54daefe2017-04-07 17:19:54 -0700138 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400139 public void setPanelView(HeightListener panelView) {
140 mPanelView = panelView;
141 }
142
143 @Override
144 public void onConfigurationChanged(Configuration newConfig) {
145 super.onConfigurationChanged(newConfig);
146 if (newConfig.getLayoutDirection() != mLayoutDirection) {
147 mLayoutDirection = newConfig.getLayoutDirection();
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800148
149 if (mQSAnimator != null) {
150 mQSAnimator.onRtlChanged();
151 }
Jason Monk0ceef212016-11-02 14:05:23 -0400152 }
153 }
154
155 @Override
156 public void setContainer(ViewGroup container) {
157 if (container instanceof NotificationsQuickSettingsContainer) {
158 mQSCustomizer.setContainer((NotificationsQuickSettingsContainer) container);
159 }
160 }
161
Anthony Chen54daefe2017-04-07 17:19:54 -0700162 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400163 public boolean isCustomizing() {
164 return mQSCustomizer.isCustomizing();
165 }
166
167 public void setHost(QSTileHost qsh) {
168 mQSPanel.setHost(qsh, mQSCustomizer);
169 mHeader.setQSPanel(mQSPanel);
Jason Monke5b770e2017-03-03 21:49:29 -0500170 mFooter.setQSPanel(mQSPanel);
Jason Monk0ceef212016-11-02 14:05:23 -0400171 mQSDetail.setHost(qsh);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800172
173 if (mQSAnimator != null) {
174 mQSAnimator.setHost(qsh);
175 }
Jason Monk0ceef212016-11-02 14:05:23 -0400176 }
177
178 private void updateQsState() {
179 final boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling
180 || mHeaderAnimating;
181 mQSPanel.setExpanded(mQsExpanded);
182 mQSDetail.setExpanded(mQsExpanded);
183 mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
184 ? View.VISIBLE
185 : View.INVISIBLE);
186 mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating)
187 || (mQsExpanded && !mStackScrollerOverscrolling));
Jason Monke5b770e2017-03-03 21:49:29 -0500188 mFooter.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
189 ? View.VISIBLE
190 : View.INVISIBLE);
191 mFooter.setExpanded((mKeyguardShowing && !mHeaderAnimating)
192 || (mQsExpanded && !mStackScrollerOverscrolling));
Jason Monk0ceef212016-11-02 14:05:23 -0400193 mQSPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
194 }
195
Jason Monk0ceef212016-11-02 14:05:23 -0400196 public QSPanel getQsPanel() {
197 return mQSPanel;
198 }
199
200 public QSCustomizer getCustomizer() {
201 return mQSCustomizer;
202 }
203
Anthony Chen54daefe2017-04-07 17:19:54 -0700204 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400205 public boolean isShowingDetail() {
206 return mQSPanel.isShowingCustomize() || mQSDetail.isShowingDetail();
207 }
208
Anthony Chen54daefe2017-04-07 17:19:54 -0700209 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400210 public void setHeaderClickable(boolean clickable) {
211 if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
Anthony Chen54daefe2017-04-07 17:19:54 -0700212
213 View expandView = mFooter.getExpandView();
214 if (expandView != null) {
215 expandView.setClickable(clickable);
216 }
Jason Monk0ceef212016-11-02 14:05:23 -0400217 }
218
Anthony Chen54daefe2017-04-07 17:19:54 -0700219 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400220 public void setExpanded(boolean expanded) {
221 if (DEBUG) Log.d(TAG, "setExpanded " + expanded);
222 mQsExpanded = expanded;
223 mQSPanel.setListening(mListening && mQsExpanded);
224 updateQsState();
225 }
226
Anthony Chen54daefe2017-04-07 17:19:54 -0700227 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400228 public void setKeyguardShowing(boolean keyguardShowing) {
229 if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
230 mKeyguardShowing = keyguardShowing;
Selim Cinek1a891a92017-12-04 17:41:27 +0100231 mLastQSExpansion = -1;
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800232
233 if (mQSAnimator != null) {
234 mQSAnimator.setOnKeyguard(keyguardShowing);
235 }
236
Jason Monk9dac1122017-05-30 15:21:29 -0400237 mFooter.setKeyguardShowing(keyguardShowing);
Jason Monk0ceef212016-11-02 14:05:23 -0400238 updateQsState();
239 }
240
Anthony Chen54daefe2017-04-07 17:19:54 -0700241 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400242 public void setOverscrolling(boolean stackScrollerOverscrolling) {
243 if (DEBUG) Log.d(TAG, "setOverscrolling " + stackScrollerOverscrolling);
244 mStackScrollerOverscrolling = stackScrollerOverscrolling;
245 updateQsState();
246 }
247
Anthony Chen54daefe2017-04-07 17:19:54 -0700248 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400249 public void setListening(boolean listening) {
250 if (DEBUG) Log.d(TAG, "setListening " + listening);
251 mListening = listening;
252 mHeader.setListening(listening);
Jason Monke5b770e2017-03-03 21:49:29 -0500253 mFooter.setListening(listening);
Jason Monk0ceef212016-11-02 14:05:23 -0400254 mQSPanel.setListening(mListening && mQsExpanded);
255 }
256
Anthony Chen54daefe2017-04-07 17:19:54 -0700257 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400258 public void setHeaderListening(boolean listening) {
259 mHeader.setListening(listening);
Jason Monke5b770e2017-03-03 21:49:29 -0500260 mFooter.setListening(listening);
Jason Monk0ceef212016-11-02 14:05:23 -0400261 }
262
Anthony Chen54daefe2017-04-07 17:19:54 -0700263 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400264 public void setQsExpansion(float expansion, float headerTranslation) {
265 if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation);
266 mContainer.setExpansion(expansion);
267 final float translationScaleY = expansion - 1;
268 if (!mHeaderAnimating) {
Jason Monk232ac2b2017-06-02 12:50:24 -0400269 int height = mHeader.getHeight();
Jason Monkb4cc7b12017-05-09 13:50:47 -0400270 getView().setTranslationY(mKeyguardShowing ? (translationScaleY * height)
Jason Monk0ceef212016-11-02 14:05:23 -0400271 : headerTranslation);
272 }
Selim Cinek1a891a92017-12-04 17:41:27 +0100273 if (expansion == mLastQSExpansion) {
274 return;
275 }
276 mLastQSExpansion = expansion;
Jason Monk0ceef212016-11-02 14:05:23 -0400277 mHeader.setExpansion(mKeyguardShowing ? 1 : expansion);
Jason Monke5b770e2017-03-03 21:49:29 -0500278 mFooter.setExpansion(mKeyguardShowing ? 1 : expansion);
Jason Monkbdefffd2017-06-26 11:22:21 -0400279 int heightDiff = mQSPanel.getBottom() - mHeader.getBottom() + mHeader.getPaddingBottom()
280 + mFooter.getHeight();
Jason Monke5b770e2017-03-03 21:49:29 -0500281 mQSPanel.setTranslationY(translationScaleY * heightDiff);
Jason Monk0ceef212016-11-02 14:05:23 -0400282 mQSDetail.setFullyExpanded(expansion == 1);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800283
284 if (mQSAnimator != null) {
285 mQSAnimator.setPosition(expansion);
286 }
Jason Monk0ceef212016-11-02 14:05:23 -0400287
288 // Set bounds on the QS panel so it doesn't run over the header.
Jason Monkbdefffd2017-06-26 11:22:21 -0400289 mQsBounds.top = (int) -mQSPanel.getTranslationY();
Jason Monk0ceef212016-11-02 14:05:23 -0400290 mQsBounds.right = mQSPanel.getWidth();
291 mQsBounds.bottom = mQSPanel.getHeight();
292 mQSPanel.setClipBounds(mQsBounds);
293 }
294
Anthony Chen54daefe2017-04-07 17:19:54 -0700295 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400296 public void animateHeaderSlidingIn(long delay) {
297 if (DEBUG) Log.d(TAG, "animateHeaderSlidingIn");
298 // If the QS is already expanded we don't need to slide in the header as it's already
299 // visible.
300 if (!mQsExpanded) {
301 mHeaderAnimating = true;
302 mDelay = delay;
303 getView().getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
304 }
305 }
306
Anthony Chen54daefe2017-04-07 17:19:54 -0700307 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400308 public void animateHeaderSlidingOut() {
309 if (DEBUG) Log.d(TAG, "animateHeaderSlidingOut");
310 mHeaderAnimating = true;
311 getView().animate().y(-mHeader.getHeight())
312 .setStartDelay(0)
313 .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
314 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
315 .setListener(new AnimatorListenerAdapter() {
316 @Override
317 public void onAnimationEnd(Animator animation) {
318 getView().animate().setListener(null);
319 mHeaderAnimating = false;
320 updateQsState();
321 }
322 })
323 .start();
324 }
325
326 @Override
Jason Monke5b770e2017-03-03 21:49:29 -0500327 public void setExpandClickListener(OnClickListener onClickListener) {
Anthony Chen54daefe2017-04-07 17:19:54 -0700328 View expandView = mFooter.getExpandView();
329
330 if (expandView != null) {
331 expandView.setOnClickListener(onClickListener);
332 }
Jason Monke5b770e2017-03-03 21:49:29 -0500333 }
334
335 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400336 public void closeDetail() {
337 mQSPanel.closeDetail();
338 }
339
340 public void notifyCustomizeChanged() {
341 // The customize state changed, so our height changed.
Jason Monk4de5d3c2017-05-24 14:57:10 -0400342 mContainer.updateExpansion();
Jason Monk0ceef212016-11-02 14:05:23 -0400343 mQSPanel.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
344 mHeader.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
Jason Monke5b770e2017-03-03 21:49:29 -0500345 mFooter.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
Jason Monk0ceef212016-11-02 14:05:23 -0400346 // Let the panel know the position changed and it needs to update where notifications
347 // and whatnot are.
348 mPanelView.onQsHeightChanged();
349 }
350
351 /**
352 * The height this view wants to be. This is different from {@link #getMeasuredHeight} such that
353 * during closing the detail panel, this already returns the smaller height.
354 */
Anthony Chen54daefe2017-04-07 17:19:54 -0700355 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400356 public int getDesiredHeight() {
357 if (mQSCustomizer.isCustomizing()) {
358 return getView().getHeight();
359 }
360 if (mQSDetail.isClosingDetail()) {
Jason Monke5b770e2017-03-03 21:49:29 -0500361 LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams();
362 int panelHeight = layoutParams.topMargin + layoutParams.bottomMargin +
Jason Monk0ceef212016-11-02 14:05:23 -0400363 + mQSPanel.getMeasuredHeight();
Jason Monk232ac2b2017-06-02 12:50:24 -0400364 return panelHeight + getView().getPaddingBottom();
Jason Monk0ceef212016-11-02 14:05:23 -0400365 } else {
Jason Monk232ac2b2017-06-02 12:50:24 -0400366 return getView().getMeasuredHeight();
Jason Monk0ceef212016-11-02 14:05:23 -0400367 }
368 }
369
370 @Override
371 public void setHeightOverride(int desiredHeight) {
Jason Monk232ac2b2017-06-02 12:50:24 -0400372 mContainer.setHeightOverride(desiredHeight);
Jason Monk0ceef212016-11-02 14:05:23 -0400373 }
374
Anthony Chen54daefe2017-04-07 17:19:54 -0700375 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400376 public int getQsMinExpansionHeight() {
Jason Monk4de5d3c2017-05-24 14:57:10 -0400377 return mHeader.getHeight();
Jason Monk0ceef212016-11-02 14:05:23 -0400378 }
379
380 @Override
381 public void hideImmediately() {
382 getView().animate().cancel();
383 getView().setY(-mHeader.getHeight());
384 }
385
386 private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
387 = new ViewTreeObserver.OnPreDrawListener() {
388 @Override
389 public boolean onPreDraw() {
390 getView().getViewTreeObserver().removeOnPreDrawListener(this);
391 getView().animate()
392 .translationY(0f)
393 .setStartDelay(mDelay)
394 .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
395 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
396 .setListener(mAnimateHeaderSlidingInListener)
397 .start();
398 getView().setY(-mHeader.getHeight());
399 return true;
400 }
401 };
402
403 private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
404 = new AnimatorListenerAdapter() {
405 @Override
406 public void onAnimationEnd(Animator animation) {
407 mHeaderAnimating = false;
408 updateQsState();
409 }
410 };
411}