Home | History | Annotate | Download | only in qs
      1 /*
      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 
     15 package com.android.systemui.qs;
     16 
     17 import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.app.Fragment;
     22 import android.content.Context;
     23 import android.content.res.Configuration;
     24 import android.graphics.Rect;
     25 import android.os.Bundle;
     26 import android.support.annotation.Nullable;
     27 import android.support.annotation.VisibleForTesting;
     28 import android.util.Log;
     29 import android.view.ContextThemeWrapper;
     30 import android.view.LayoutInflater;
     31 import android.view.MotionEvent;
     32 import android.view.View;
     33 import android.view.View.OnClickListener;
     34 import android.view.ViewGroup;
     35 import android.view.ViewTreeObserver;
     36 import android.widget.FrameLayout.LayoutParams;
     37 
     38 import com.android.systemui.Dependency;
     39 import com.android.systemui.Interpolators;
     40 import com.android.systemui.R;
     41 import com.android.systemui.R.id;
     42 import com.android.systemui.SysUiServiceProvider;
     43 import com.android.systemui.plugins.qs.QS;
     44 import com.android.systemui.qs.customize.QSCustomizer;
     45 import com.android.systemui.statusbar.CommandQueue;
     46 import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
     47 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
     48 import com.android.systemui.statusbar.stack.StackStateAnimator;
     49 
     50 public class QSFragment extends Fragment implements QS, CommandQueue.Callbacks {
     51     private static final String TAG = "QS";
     52     private static final boolean DEBUG = false;
     53     private static final String EXTRA_EXPANDED = "expanded";
     54     private static final String EXTRA_LISTENING = "listening";
     55 
     56     private final Rect mQsBounds = new Rect();
     57     private boolean mQsExpanded;
     58     private boolean mHeaderAnimating;
     59     private boolean mKeyguardShowing;
     60     private boolean mStackScrollerOverscrolling;
     61 
     62     private long mDelay;
     63 
     64     private QSAnimator mQSAnimator;
     65     private HeightListener mPanelView;
     66     protected QuickStatusBarHeader mHeader;
     67     private QSCustomizer mQSCustomizer;
     68     protected QSPanel mQSPanel;
     69     private QSDetail mQSDetail;
     70     private boolean mListening;
     71     private QSContainerImpl mContainer;
     72     private int mLayoutDirection;
     73     private QSFooter mFooter;
     74     private float mLastQSExpansion = -1;
     75     private boolean mQsDisabled;
     76 
     77     private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler =
     78             Dependency.get(RemoteInputQuickSettingsDisabler.class);
     79 
     80     @Override
     81     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
     82             Bundle savedInstanceState) {
     83         inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(), R.style.qs_theme));
     84         return inflater.inflate(R.layout.qs_panel, container, false);
     85     }
     86 
     87     @Override
     88     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
     89         super.onViewCreated(view, savedInstanceState);
     90         mQSPanel = view.findViewById(R.id.quick_settings_panel);
     91         mQSDetail = view.findViewById(R.id.qs_detail);
     92         mHeader = view.findViewById(R.id.header);
     93         mFooter = view.findViewById(R.id.qs_footer);
     94         mContainer = view.findViewById(id.quick_settings_container);
     95 
     96         mQSDetail.setQsPanel(mQSPanel, mHeader, (View) mFooter);
     97         mQSAnimator = new QSAnimator(this,
     98                 mHeader.findViewById(R.id.quick_qs_panel), mQSPanel);
     99 
    100         mQSCustomizer = view.findViewById(R.id.qs_customize);
    101         mQSCustomizer.setQs(this);
    102         if (savedInstanceState != null) {
    103             setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED));
    104             setListening(savedInstanceState.getBoolean(EXTRA_LISTENING));
    105             int[] loc = new int[2];
    106             View edit = view.findViewById(android.R.id.edit);
    107             edit.getLocationInWindow(loc);
    108             int x = loc[0] + edit.getWidth() / 2;
    109             int y = loc[1] + edit.getHeight() / 2;
    110             mQSCustomizer.setEditLocation(x, y);
    111             mQSCustomizer.restoreInstanceState(savedInstanceState);
    112         }
    113         SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this);
    114     }
    115 
    116     @Override
    117     public void onDestroyView() {
    118         SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this);
    119         super.onDestroyView();
    120     }
    121 
    122     @Override
    123     public void onDestroy() {
    124         super.onDestroy();
    125         if (mListening) {
    126             setListening(false);
    127         }
    128     }
    129 
    130     @Override
    131     public void onSaveInstanceState(Bundle outState) {
    132         super.onSaveInstanceState(outState);
    133         outState.putBoolean(EXTRA_EXPANDED, mQsExpanded);
    134         outState.putBoolean(EXTRA_LISTENING, mListening);
    135         mQSCustomizer.saveInstanceState(outState);
    136     }
    137 
    138     @VisibleForTesting
    139     boolean isListening() {
    140         return mListening;
    141     }
    142 
    143     @VisibleForTesting
    144     boolean isExpanded() {
    145         return mQsExpanded;
    146     }
    147 
    148     @Override
    149     public View getHeader() {
    150         return mHeader;
    151     }
    152 
    153     @Override
    154     public void setHasNotifications(boolean hasNotifications) {
    155     }
    156 
    157     @Override
    158     public void setPanelView(HeightListener panelView) {
    159         mPanelView = panelView;
    160     }
    161 
    162     @Override
    163     public void onConfigurationChanged(Configuration newConfig) {
    164         super.onConfigurationChanged(newConfig);
    165         if (newConfig.getLayoutDirection() != mLayoutDirection) {
    166             mLayoutDirection = newConfig.getLayoutDirection();
    167 
    168             if (mQSAnimator != null) {
    169                 mQSAnimator.onRtlChanged();
    170             }
    171         }
    172     }
    173 
    174     @Override
    175     public void setContainer(ViewGroup container) {
    176         if (container instanceof NotificationsQuickSettingsContainer) {
    177             mQSCustomizer.setContainer((NotificationsQuickSettingsContainer) container);
    178         }
    179     }
    180 
    181     @Override
    182     public boolean isCustomizing() {
    183         return mQSCustomizer.isCustomizing();
    184     }
    185 
    186     public void setHost(QSTileHost qsh) {
    187         mQSPanel.setHost(qsh, mQSCustomizer);
    188         mHeader.setQSPanel(mQSPanel);
    189         mFooter.setQSPanel(mQSPanel);
    190         mQSDetail.setHost(qsh);
    191 
    192         if (mQSAnimator != null) {
    193             mQSAnimator.setHost(qsh);
    194         }
    195     }
    196 
    197     @Override
    198     public void disable(int state1, int state2, boolean animate) {
    199         state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2);
    200 
    201         final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
    202         if (disabled == mQsDisabled) return;
    203         mQsDisabled = disabled;
    204         mContainer.disable(state1, state2, animate);
    205         mHeader.disable(state1, state2, animate);
    206         mFooter.disable(state1, state2, animate);
    207         updateQsState();
    208     }
    209 
    210     private void updateQsState() {
    211         final boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling
    212                 || mHeaderAnimating;
    213         mQSPanel.setExpanded(mQsExpanded);
    214         mQSDetail.setExpanded(mQsExpanded);
    215         mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
    216                 ? View.VISIBLE
    217                 : View.INVISIBLE);
    218         mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating)
    219                 || (mQsExpanded && !mStackScrollerOverscrolling));
    220         mFooter.setVisibility(
    221                 !mQsDisabled && (mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
    222                 ? View.VISIBLE
    223                 : View.INVISIBLE);
    224         mFooter.setExpanded((mKeyguardShowing && !mHeaderAnimating)
    225                 || (mQsExpanded && !mStackScrollerOverscrolling));
    226         mQSPanel.setVisibility(!mQsDisabled && expandVisually ? View.VISIBLE : View.INVISIBLE);
    227     }
    228 
    229     public QSPanel getQsPanel() {
    230         return mQSPanel;
    231     }
    232 
    233     public QSCustomizer getCustomizer() {
    234         return mQSCustomizer;
    235     }
    236 
    237     @Override
    238     public boolean isShowingDetail() {
    239         return mQSPanel.isShowingCustomize() || mQSDetail.isShowingDetail();
    240     }
    241 
    242     @Override
    243     public boolean onInterceptTouchEvent(MotionEvent event) {
    244         return isCustomizing();
    245     }
    246 
    247     @Override
    248     public void setHeaderClickable(boolean clickable) {
    249         if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
    250     }
    251 
    252     @Override
    253     public void setExpanded(boolean expanded) {
    254         if (DEBUG) Log.d(TAG, "setExpanded " + expanded);
    255         mQsExpanded = expanded;
    256         mQSPanel.setListening(mListening && mQsExpanded);
    257         updateQsState();
    258     }
    259 
    260     @Override
    261     public void setKeyguardShowing(boolean keyguardShowing) {
    262         if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
    263         mKeyguardShowing = keyguardShowing;
    264         mLastQSExpansion = -1;
    265 
    266         if (mQSAnimator != null) {
    267             mQSAnimator.setOnKeyguard(keyguardShowing);
    268         }
    269 
    270         mFooter.setKeyguardShowing(keyguardShowing);
    271         updateQsState();
    272     }
    273 
    274     @Override
    275     public void setOverscrolling(boolean stackScrollerOverscrolling) {
    276         if (DEBUG) Log.d(TAG, "setOverscrolling " + stackScrollerOverscrolling);
    277         mStackScrollerOverscrolling = stackScrollerOverscrolling;
    278         updateQsState();
    279     }
    280 
    281     @Override
    282     public void setListening(boolean listening) {
    283         if (DEBUG) Log.d(TAG, "setListening " + listening);
    284         mListening = listening;
    285         mHeader.setListening(listening);
    286         mFooter.setListening(listening);
    287         mQSPanel.setListening(mListening && mQsExpanded);
    288     }
    289 
    290     @Override
    291     public void setHeaderListening(boolean listening) {
    292         mHeader.setListening(listening);
    293         mFooter.setListening(listening);
    294     }
    295 
    296     @Override
    297     public void setQsExpansion(float expansion, float headerTranslation) {
    298         if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation);
    299         mContainer.setExpansion(expansion);
    300         final float translationScaleY = expansion - 1;
    301         if (!mHeaderAnimating) {
    302             getView().setTranslationY(
    303                     mKeyguardShowing
    304                             ? translationScaleY * mHeader.getHeight()
    305                             : headerTranslation);
    306         }
    307         if (expansion == mLastQSExpansion) {
    308             return;
    309         }
    310         mLastQSExpansion = expansion;
    311 
    312         boolean fullyExpanded = expansion == 1;
    313         int heightDiff = mQSPanel.getBottom() - mHeader.getBottom() + mHeader.getPaddingBottom()
    314                 + mFooter.getHeight();
    315         float panelTranslationY = translationScaleY * heightDiff;
    316 
    317         // Let the views animate their contents correctly by giving them the necessary context.
    318         mHeader.setExpansion(mKeyguardShowing, expansion, panelTranslationY);
    319         mFooter.setExpansion(mKeyguardShowing ? 1 : expansion);
    320         mQSPanel.getQsTileRevealController().setExpansion(expansion);
    321         mQSPanel.getTileLayout().setExpansion(expansion);
    322         mQSPanel.setTranslationY(translationScaleY * heightDiff);
    323         mQSDetail.setFullyExpanded(fullyExpanded);
    324 
    325         if (fullyExpanded) {
    326             // Always draw within the bounds of the view when fully expanded.
    327             mQSPanel.setClipBounds(null);
    328         } else {
    329             // Set bounds on the QS panel so it doesn't run over the header when animating.
    330             mQsBounds.top = (int) -mQSPanel.getTranslationY();
    331             mQsBounds.right = mQSPanel.getWidth();
    332             mQsBounds.bottom = mQSPanel.getHeight();
    333             mQSPanel.setClipBounds(mQsBounds);
    334         }
    335 
    336         if (mQSAnimator != null) {
    337             mQSAnimator.setPosition(expansion);
    338         }
    339     }
    340 
    341     @Override
    342     public void animateHeaderSlidingIn(long delay) {
    343         if (DEBUG) Log.d(TAG, "animateHeaderSlidingIn");
    344         // If the QS is already expanded we don't need to slide in the header as it's already
    345         // visible.
    346         if (!mQsExpanded) {
    347             mHeaderAnimating = true;
    348             mDelay = delay;
    349             getView().getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
    350         }
    351     }
    352 
    353     @Override
    354     public void animateHeaderSlidingOut() {
    355         if (DEBUG) Log.d(TAG, "animateHeaderSlidingOut");
    356         mHeaderAnimating = true;
    357         getView().animate().y(-mHeader.getHeight())
    358                 .setStartDelay(0)
    359                 .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
    360                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
    361                 .setListener(new AnimatorListenerAdapter() {
    362                     @Override
    363                     public void onAnimationEnd(Animator animation) {
    364                         getView().animate().setListener(null);
    365                         mHeaderAnimating = false;
    366                         updateQsState();
    367                     }
    368                 })
    369                 .start();
    370     }
    371 
    372     @Override
    373     public void setExpandClickListener(OnClickListener onClickListener) {
    374         mFooter.setExpandClickListener(onClickListener);
    375     }
    376 
    377     @Override
    378     public void closeDetail() {
    379         mQSPanel.closeDetail();
    380     }
    381 
    382     public void notifyCustomizeChanged() {
    383         // The customize state changed, so our height changed.
    384         mContainer.updateExpansion();
    385         mQSPanel.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
    386         mFooter.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
    387         // Let the panel know the position changed and it needs to update where notifications
    388         // and whatnot are.
    389         mPanelView.onQsHeightChanged();
    390     }
    391 
    392     /**
    393      * The height this view wants to be. This is different from {@link #getMeasuredHeight} such that
    394      * during closing the detail panel, this already returns the smaller height.
    395      */
    396     @Override
    397     public int getDesiredHeight() {
    398         if (mQSCustomizer.isCustomizing()) {
    399             return getView().getHeight();
    400         }
    401         if (mQSDetail.isClosingDetail()) {
    402             LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams();
    403             int panelHeight = layoutParams.topMargin + layoutParams.bottomMargin +
    404                     + mQSPanel.getMeasuredHeight();
    405             return panelHeight + getView().getPaddingBottom();
    406         } else {
    407             return getView().getMeasuredHeight();
    408         }
    409     }
    410 
    411     @Override
    412     public void setHeightOverride(int desiredHeight) {
    413         mContainer.setHeightOverride(desiredHeight);
    414     }
    415 
    416     @Override
    417     public int getQsMinExpansionHeight() {
    418         return mHeader.getHeight();
    419     }
    420 
    421     @Override
    422     public void hideImmediately() {
    423         getView().animate().cancel();
    424         getView().setY(-mHeader.getHeight());
    425     }
    426 
    427     private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
    428             = new ViewTreeObserver.OnPreDrawListener() {
    429         @Override
    430         public boolean onPreDraw() {
    431             getView().getViewTreeObserver().removeOnPreDrawListener(this);
    432             getView().animate()
    433                     .translationY(0f)
    434                     .setStartDelay(mDelay)
    435                     .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
    436                     .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
    437                     .setListener(mAnimateHeaderSlidingInListener)
    438                     .start();
    439             getView().setY(-mHeader.getHeight());
    440             return true;
    441         }
    442     };
    443 
    444     private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
    445             = new AnimatorListenerAdapter() {
    446         @Override
    447         public void onAnimationEnd(Animator animation) {
    448             mHeaderAnimating = false;
    449             updateQsState();
    450         }
    451     };
    452 }
    453