/* * Copyright (C) 2016 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.annotation.Nullable; import android.app.Fragment; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.support.annotation.VisibleForTesting; import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout.LayoutParams; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.R.id; import com.android.systemui.plugins.qs.QS; import com.android.systemui.qs.customize.QSCustomizer; import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; import com.android.systemui.statusbar.stack.StackStateAnimator; public class QSFragment extends Fragment implements QS { private static final String TAG = "QS"; private static final boolean DEBUG = false; private static final String EXTRA_EXPANDED = "expanded"; private static final String EXTRA_LISTENING = "listening"; private final Rect mQsBounds = new Rect(); private boolean mQsExpanded; private boolean mHeaderAnimating; private boolean mKeyguardShowing; private boolean mStackScrollerOverscrolling; private long mDelay; private QSAnimator mQSAnimator; private HeightListener mPanelView; protected QuickStatusBarHeader mHeader; private QSCustomizer mQSCustomizer; protected QSPanel mQSPanel; private QSDetail mQSDetail; private boolean mListening; private QSContainerImpl mContainer; private int mLayoutDirection; private QSFooter mFooter; private int mGutterHeight; @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { inflater =inflater.cloneInContext(new ContextThemeWrapper(getContext(), R.style.qs_theme)); return inflater.inflate(R.layout.qs_panel, container, false); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mQSPanel = view.findViewById(R.id.quick_settings_panel); mQSDetail = view.findViewById(R.id.qs_detail); mHeader = view.findViewById(R.id.header); mFooter = view.findViewById(R.id.qs_footer); mContainer = view.findViewById(id.quick_settings_container); mGutterHeight = getContext().getResources().getDimensionPixelSize(R.dimen.qs_gutter_height); mQSDetail.setQsPanel(mQSPanel, mHeader, mFooter); // If the quick settings row is not shown, then there is no need for the animation from // the row to the full QS panel. if (getResources().getBoolean(R.bool.config_showQuickSettingsRow)) { mQSAnimator = new QSAnimator(this, mHeader.findViewById(R.id.quick_qs_panel), mQSPanel); } mQSCustomizer = view.findViewById(R.id.qs_customize); mQSCustomizer.setQs(this); if (savedInstanceState != null) { setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED)); setListening(savedInstanceState.getBoolean(EXTRA_LISTENING)); } } @Override public void onDestroy() { super.onDestroy(); if (mListening) { setListening(false); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(EXTRA_EXPANDED, mQsExpanded); outState.putBoolean(EXTRA_LISTENING, mListening); } @VisibleForTesting boolean isListening() { return mListening; } @VisibleForTesting boolean isExpanded() { return mQsExpanded; } @Override public View getHeader() { return mHeader; } @Override public void setHasNotifications(boolean hasNotifications) { mContainer.setGutterEnabled(hasNotifications); } public void setPanelView(HeightListener panelView) { mPanelView = panelView; } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (newConfig.getLayoutDirection() != mLayoutDirection) { mLayoutDirection = newConfig.getLayoutDirection(); if (mQSAnimator != null) { mQSAnimator.onRtlChanged(); } } } @Override public void setContainer(ViewGroup container) { if (container instanceof NotificationsQuickSettingsContainer) { mQSCustomizer.setContainer((NotificationsQuickSettingsContainer) container); } } public boolean isCustomizing() { return mQSCustomizer.isCustomizing(); } public void setHost(QSTileHost qsh) { mQSPanel.setHost(qsh, mQSCustomizer); mHeader.setQSPanel(mQSPanel); mFooter.setQSPanel(mQSPanel); mQSDetail.setHost(qsh); if (mQSAnimator != null) { mQSAnimator.setHost(qsh); } } private void updateQsState() { final 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)); mFooter.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating) ? View.VISIBLE : View.INVISIBLE); mFooter.setExpanded((mKeyguardShowing && !mHeaderAnimating) || (mQsExpanded && !mStackScrollerOverscrolling)); mQSPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE); } 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); mFooter.getExpandView().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; if (mQSAnimator != null) { mQSAnimator.setOnKeyguard(keyguardShowing); } mFooter.setKeyguardShowing(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); mFooter.setListening(listening); mQSPanel.setListening(mListening && mQsExpanded); } public void setHeaderListening(boolean listening) { mHeader.setListening(listening); mFooter.setListening(listening); } public void setQsExpansion(float expansion, float headerTranslation) { if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation); mContainer.setExpansion(expansion); final float translationScaleY = expansion - 1; if (!mHeaderAnimating) { int height = mHeader.getHeight() + mGutterHeight; getView().setTranslationY(mKeyguardShowing ? (translationScaleY * height) : headerTranslation); } mHeader.setExpansion(mKeyguardShowing ? 1 : expansion); mFooter.setExpansion(mKeyguardShowing ? 1 : expansion); int heightDiff = mQSPanel.getBottom() - mHeader.getBottom() + mHeader.getPaddingBottom() + mFooter.getHeight(); mQSPanel.setTranslationY(translationScaleY * heightDiff); mQSDetail.setFullyExpanded(expansion == 1); if (mQSAnimator != null) { mQSAnimator.setPosition(expansion); } // Set bounds on the QS panel so it doesn't run over the header. mQsBounds.top = (int) -mQSPanel.getTranslationY(); 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; getView().getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn); } } public void animateHeaderSlidingOut() { if (DEBUG) Log.d(TAG, "animateHeaderSlidingOut"); mHeaderAnimating = true; getView().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) { getView().animate().setListener(null); mHeaderAnimating = false; updateQsState(); } }) .start(); } @Override public void setExpandClickListener(OnClickListener onClickListener) { mFooter.getExpandView().setOnClickListener(onClickListener); } @Override public void closeDetail() { mQSPanel.closeDetail(); } public void notifyCustomizeChanged() { // The customize state changed, so our height changed. mContainer.updateExpansion(); mQSPanel.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE); mHeader.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE); mFooter.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(); } /** * 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 (mQSCustomizer.isCustomizing()) { return getView().getHeight(); } if (mQSDetail.isClosingDetail()) { LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams(); int panelHeight = layoutParams.topMargin + layoutParams.bottomMargin + + mQSPanel.getMeasuredHeight(); return panelHeight + getView().getPaddingBottom() + mGutterHeight; } else { return getView().getMeasuredHeight() + mGutterHeight; } } @Override public void setHeightOverride(int desiredHeight) { mContainer.setHeightOverride(desiredHeight - mGutterHeight); } public int getQsMinExpansionHeight() { return mHeader.getHeight(); } @Override public void hideImmediately() { getView().animate().cancel(); getView().setY(-mHeader.getHeight()); } private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn = new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { getView().getViewTreeObserver().removeOnPreDrawListener(this); getView().animate() .translationY(0f) .setStartDelay(mDelay) .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .setListener(mAnimateHeaderSlidingInListener) .start(); getView().setY(-mHeader.getHeight()); return true; } }; private final Animator.AnimatorListener mAnimateHeaderSlidingInListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mHeaderAnimating = false; updateQsState(); } }; }