blob: 29a8f1641a8553f1de2bdc5ac301b0ff5d2b9a13 [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;
Jason Monk231b0522018-01-04 10:49:55 -050028import android.view.MotionEvent;
Jason Monk0ceef212016-11-02 14:05:23 -040029import android.view.View;
Jason Monke5b770e2017-03-03 21:49:29 -050030import android.view.View.OnClickListener;
Jason Monk0ceef212016-11-02 14:05:23 -040031import android.view.ViewGroup;
32import android.view.ViewTreeObserver;
33import android.widget.FrameLayout.LayoutParams;
34
35import com.android.systemui.Interpolators;
36import com.android.systemui.R;
Jason Monkb4cc7b12017-05-09 13:50:47 -040037import com.android.systemui.R.id;
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;
Selim Cinek1a891a92017-12-04 17:41:27 +010067 private float mLastQSExpansion = -1;
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);
Jason Monk0ceef212016-11-02 14:05:23 -040084
Anthony Chen54daefe2017-04-07 17:19:54 -070085 mQSDetail.setQsPanel(mQSPanel, mHeader, (View) mFooter);
86 mQSAnimator = new QSAnimator(this,
87 mHeader.findViewById(R.id.quick_qs_panel), mQSPanel);
Anthony Chen3cb3ad92016-12-01 10:58:47 -080088
Jason Monke5b770e2017-03-03 21:49:29 -050089 mQSCustomizer = view.findViewById(R.id.qs_customize);
Jason Monk0ceef212016-11-02 14:05:23 -040090 mQSCustomizer.setQs(this);
Jason Monk64b214e2017-03-27 13:40:59 -040091 if (savedInstanceState != null) {
92 setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED));
93 setListening(savedInstanceState.getBoolean(EXTRA_LISTENING));
Selim Cinek707e2072017-06-30 18:32:40 +020094 int[] loc = new int[2];
95 View edit = view.findViewById(android.R.id.edit);
96 edit.getLocationInWindow(loc);
97 int x = loc[0] + edit.getWidth() / 2;
98 int y = loc[1] + edit.getHeight() / 2;
99 mQSCustomizer.setEditLocation(x, y);
100 mQSCustomizer.restoreInstanceState(savedInstanceState);
Jason Monk64b214e2017-03-27 13:40:59 -0400101 }
102 }
103
104 @Override
105 public void onDestroy() {
106 super.onDestroy();
107 if (mListening) {
108 setListening(false);
109 }
110 }
111
112 @Override
113 public void onSaveInstanceState(Bundle outState) {
114 super.onSaveInstanceState(outState);
115 outState.putBoolean(EXTRA_EXPANDED, mQsExpanded);
116 outState.putBoolean(EXTRA_LISTENING, mListening);
Selim Cinek707e2072017-06-30 18:32:40 +0200117 mQSCustomizer.saveInstanceState(outState);
Jason Monk64b214e2017-03-27 13:40:59 -0400118 }
119
120 @VisibleForTesting
121 boolean isListening() {
122 return mListening;
123 }
124
125 @VisibleForTesting
126 boolean isExpanded() {
127 return mQsExpanded;
Jason Monk0ceef212016-11-02 14:05:23 -0400128 }
129
Jason Monke5b770e2017-03-03 21:49:29 -0500130 @Override
131 public View getHeader() {
132 return mHeader;
133 }
134
Jason Monk4de5d3c2017-05-24 14:57:10 -0400135 @Override
136 public void setHasNotifications(boolean hasNotifications) {
Jason Monk4de5d3c2017-05-24 14:57:10 -0400137 }
138
Anthony Chen54daefe2017-04-07 17:19:54 -0700139 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400140 public void setPanelView(HeightListener panelView) {
141 mPanelView = panelView;
142 }
143
144 @Override
145 public void onConfigurationChanged(Configuration newConfig) {
146 super.onConfigurationChanged(newConfig);
147 if (newConfig.getLayoutDirection() != mLayoutDirection) {
148 mLayoutDirection = newConfig.getLayoutDirection();
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800149
150 if (mQSAnimator != null) {
151 mQSAnimator.onRtlChanged();
152 }
Jason Monk0ceef212016-11-02 14:05:23 -0400153 }
154 }
155
156 @Override
157 public void setContainer(ViewGroup container) {
158 if (container instanceof NotificationsQuickSettingsContainer) {
159 mQSCustomizer.setContainer((NotificationsQuickSettingsContainer) container);
160 }
161 }
162
Anthony Chen54daefe2017-04-07 17:19:54 -0700163 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400164 public boolean isCustomizing() {
165 return mQSCustomizer.isCustomizing();
166 }
167
168 public void setHost(QSTileHost qsh) {
169 mQSPanel.setHost(qsh, mQSCustomizer);
170 mHeader.setQSPanel(mQSPanel);
Jason Monke5b770e2017-03-03 21:49:29 -0500171 mFooter.setQSPanel(mQSPanel);
Jason Monk0ceef212016-11-02 14:05:23 -0400172 mQSDetail.setHost(qsh);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800173
174 if (mQSAnimator != null) {
175 mQSAnimator.setHost(qsh);
176 }
Jason Monk0ceef212016-11-02 14:05:23 -0400177 }
178
179 private void updateQsState() {
180 final boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling
181 || mHeaderAnimating;
182 mQSPanel.setExpanded(mQsExpanded);
183 mQSDetail.setExpanded(mQsExpanded);
184 mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
185 ? View.VISIBLE
186 : View.INVISIBLE);
187 mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating)
188 || (mQsExpanded && !mStackScrollerOverscrolling));
Jason Monke5b770e2017-03-03 21:49:29 -0500189 mFooter.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
190 ? View.VISIBLE
191 : View.INVISIBLE);
192 mFooter.setExpanded((mKeyguardShowing && !mHeaderAnimating)
193 || (mQsExpanded && !mStackScrollerOverscrolling));
Jason Monk0ceef212016-11-02 14:05:23 -0400194 mQSPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
195 }
196
Jason Monk0ceef212016-11-02 14:05:23 -0400197 public QSPanel getQsPanel() {
198 return mQSPanel;
199 }
200
201 public QSCustomizer getCustomizer() {
202 return mQSCustomizer;
203 }
204
Anthony Chen54daefe2017-04-07 17:19:54 -0700205 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400206 public boolean isShowingDetail() {
207 return mQSPanel.isShowingCustomize() || mQSDetail.isShowingDetail();
208 }
209
Anthony Chen54daefe2017-04-07 17:19:54 -0700210 @Override
Jason Monk231b0522018-01-04 10:49:55 -0500211 public boolean onInterceptTouchEvent(MotionEvent event) {
212 return isCustomizing() || mQSPanel.onInterceptTouchEvent(event);
213 }
214
215 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400216 public void setHeaderClickable(boolean clickable) {
217 if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
Anthony Chen54daefe2017-04-07 17:19:54 -0700218
219 View expandView = mFooter.getExpandView();
220 if (expandView != null) {
221 expandView.setClickable(clickable);
222 }
Jason Monk0ceef212016-11-02 14:05:23 -0400223 }
224
Anthony Chen54daefe2017-04-07 17:19:54 -0700225 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400226 public void setExpanded(boolean expanded) {
227 if (DEBUG) Log.d(TAG, "setExpanded " + expanded);
228 mQsExpanded = expanded;
229 mQSPanel.setListening(mListening && mQsExpanded);
230 updateQsState();
231 }
232
Anthony Chen54daefe2017-04-07 17:19:54 -0700233 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400234 public void setKeyguardShowing(boolean keyguardShowing) {
235 if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
236 mKeyguardShowing = keyguardShowing;
Selim Cinek1a891a92017-12-04 17:41:27 +0100237 mLastQSExpansion = -1;
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800238
239 if (mQSAnimator != null) {
240 mQSAnimator.setOnKeyguard(keyguardShowing);
241 }
242
Jason Monk9dac1122017-05-30 15:21:29 -0400243 mFooter.setKeyguardShowing(keyguardShowing);
Jason Monk0ceef212016-11-02 14:05:23 -0400244 updateQsState();
245 }
246
Anthony Chen54daefe2017-04-07 17:19:54 -0700247 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400248 public void setOverscrolling(boolean stackScrollerOverscrolling) {
249 if (DEBUG) Log.d(TAG, "setOverscrolling " + stackScrollerOverscrolling);
250 mStackScrollerOverscrolling = stackScrollerOverscrolling;
251 updateQsState();
252 }
253
Anthony Chen54daefe2017-04-07 17:19:54 -0700254 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400255 public void setListening(boolean listening) {
256 if (DEBUG) Log.d(TAG, "setListening " + listening);
257 mListening = listening;
258 mHeader.setListening(listening);
Jason Monke5b770e2017-03-03 21:49:29 -0500259 mFooter.setListening(listening);
Jason Monk0ceef212016-11-02 14:05:23 -0400260 mQSPanel.setListening(mListening && mQsExpanded);
261 }
262
Anthony Chen54daefe2017-04-07 17:19:54 -0700263 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400264 public void setHeaderListening(boolean listening) {
265 mHeader.setListening(listening);
Jason Monke5b770e2017-03-03 21:49:29 -0500266 mFooter.setListening(listening);
Jason Monk0ceef212016-11-02 14:05:23 -0400267 }
268
Anthony Chen54daefe2017-04-07 17:19:54 -0700269 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400270 public void setQsExpansion(float expansion, float headerTranslation) {
271 if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation);
272 mContainer.setExpansion(expansion);
273 final float translationScaleY = expansion - 1;
274 if (!mHeaderAnimating) {
Jason Monk232ac2b2017-06-02 12:50:24 -0400275 int height = mHeader.getHeight();
Jason Monkb4cc7b12017-05-09 13:50:47 -0400276 getView().setTranslationY(mKeyguardShowing ? (translationScaleY * height)
Jason Monk0ceef212016-11-02 14:05:23 -0400277 : headerTranslation);
278 }
Selim Cinek1a891a92017-12-04 17:41:27 +0100279 if (expansion == mLastQSExpansion) {
280 return;
281 }
282 mLastQSExpansion = expansion;
Jason Monk0ceef212016-11-02 14:05:23 -0400283 mHeader.setExpansion(mKeyguardShowing ? 1 : expansion);
Jason Monke5b770e2017-03-03 21:49:29 -0500284 mFooter.setExpansion(mKeyguardShowing ? 1 : expansion);
Jason Monkbdefffd2017-06-26 11:22:21 -0400285 int heightDiff = mQSPanel.getBottom() - mHeader.getBottom() + mHeader.getPaddingBottom()
286 + mFooter.getHeight();
Jason Monke5b770e2017-03-03 21:49:29 -0500287 mQSPanel.setTranslationY(translationScaleY * heightDiff);
Jason Monk0ceef212016-11-02 14:05:23 -0400288 mQSDetail.setFullyExpanded(expansion == 1);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800289
290 if (mQSAnimator != null) {
291 mQSAnimator.setPosition(expansion);
292 }
Jason Monk0ceef212016-11-02 14:05:23 -0400293
294 // Set bounds on the QS panel so it doesn't run over the header.
Jason Monkbdefffd2017-06-26 11:22:21 -0400295 mQsBounds.top = (int) -mQSPanel.getTranslationY();
Jason Monk0ceef212016-11-02 14:05:23 -0400296 mQsBounds.right = mQSPanel.getWidth();
297 mQsBounds.bottom = mQSPanel.getHeight();
298 mQSPanel.setClipBounds(mQsBounds);
299 }
300
Anthony Chen54daefe2017-04-07 17:19:54 -0700301 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400302 public void animateHeaderSlidingIn(long delay) {
303 if (DEBUG) Log.d(TAG, "animateHeaderSlidingIn");
304 // If the QS is already expanded we don't need to slide in the header as it's already
305 // visible.
306 if (!mQsExpanded) {
307 mHeaderAnimating = true;
308 mDelay = delay;
309 getView().getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
310 }
311 }
312
Anthony Chen54daefe2017-04-07 17:19:54 -0700313 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400314 public void animateHeaderSlidingOut() {
315 if (DEBUG) Log.d(TAG, "animateHeaderSlidingOut");
316 mHeaderAnimating = true;
317 getView().animate().y(-mHeader.getHeight())
318 .setStartDelay(0)
319 .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
320 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
321 .setListener(new AnimatorListenerAdapter() {
322 @Override
323 public void onAnimationEnd(Animator animation) {
324 getView().animate().setListener(null);
325 mHeaderAnimating = false;
326 updateQsState();
327 }
328 })
329 .start();
330 }
331
332 @Override
Jason Monke5b770e2017-03-03 21:49:29 -0500333 public void setExpandClickListener(OnClickListener onClickListener) {
Anthony Chen54daefe2017-04-07 17:19:54 -0700334 View expandView = mFooter.getExpandView();
335
336 if (expandView != null) {
337 expandView.setOnClickListener(onClickListener);
338 }
Jason Monke5b770e2017-03-03 21:49:29 -0500339 }
340
341 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400342 public void closeDetail() {
343 mQSPanel.closeDetail();
344 }
345
346 public void notifyCustomizeChanged() {
347 // The customize state changed, so our height changed.
Jason Monk4de5d3c2017-05-24 14:57:10 -0400348 mContainer.updateExpansion();
Jason Monk0ceef212016-11-02 14:05:23 -0400349 mQSPanel.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
350 mHeader.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
Jason Monke5b770e2017-03-03 21:49:29 -0500351 mFooter.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
Jason Monk0ceef212016-11-02 14:05:23 -0400352 // Let the panel know the position changed and it needs to update where notifications
353 // and whatnot are.
354 mPanelView.onQsHeightChanged();
355 }
356
357 /**
358 * The height this view wants to be. This is different from {@link #getMeasuredHeight} such that
359 * during closing the detail panel, this already returns the smaller height.
360 */
Anthony Chen54daefe2017-04-07 17:19:54 -0700361 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400362 public int getDesiredHeight() {
363 if (mQSCustomizer.isCustomizing()) {
364 return getView().getHeight();
365 }
366 if (mQSDetail.isClosingDetail()) {
Jason Monke5b770e2017-03-03 21:49:29 -0500367 LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams();
368 int panelHeight = layoutParams.topMargin + layoutParams.bottomMargin +
Jason Monk0ceef212016-11-02 14:05:23 -0400369 + mQSPanel.getMeasuredHeight();
Jason Monk232ac2b2017-06-02 12:50:24 -0400370 return panelHeight + getView().getPaddingBottom();
Jason Monk0ceef212016-11-02 14:05:23 -0400371 } else {
Jason Monk232ac2b2017-06-02 12:50:24 -0400372 return getView().getMeasuredHeight();
Jason Monk0ceef212016-11-02 14:05:23 -0400373 }
374 }
375
376 @Override
377 public void setHeightOverride(int desiredHeight) {
Jason Monk232ac2b2017-06-02 12:50:24 -0400378 mContainer.setHeightOverride(desiredHeight);
Jason Monk0ceef212016-11-02 14:05:23 -0400379 }
380
Anthony Chen54daefe2017-04-07 17:19:54 -0700381 @Override
Jason Monk0ceef212016-11-02 14:05:23 -0400382 public int getQsMinExpansionHeight() {
Jason Monk4de5d3c2017-05-24 14:57:10 -0400383 return mHeader.getHeight();
Jason Monk0ceef212016-11-02 14:05:23 -0400384 }
385
386 @Override
387 public void hideImmediately() {
388 getView().animate().cancel();
389 getView().setY(-mHeader.getHeight());
390 }
391
392 private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
393 = new ViewTreeObserver.OnPreDrawListener() {
394 @Override
395 public boolean onPreDraw() {
396 getView().getViewTreeObserver().removeOnPreDrawListener(this);
397 getView().animate()
398 .translationY(0f)
399 .setStartDelay(mDelay)
400 .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
401 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
402 .setListener(mAnimateHeaderSlidingInListener)
403 .start();
404 getView().setY(-mHeader.getHeight());
405 return true;
406 }
407 };
408
409 private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
410 = new AnimatorListenerAdapter() {
411 @Override
412 public void onAnimationEnd(Animator animation) {
413 mHeaderAnimating = false;
414 updateQsState();
415 }
416 };
417}