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 android.animation.Animator;
     18 import android.animation.Animator.AnimatorListener;
     19 import android.animation.AnimatorListenerAdapter;
     20 import android.annotation.Nullable;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.res.Configuration;
     24 import android.graphics.drawable.Animatable;
     25 import android.util.AttributeSet;
     26 import android.util.SparseArray;
     27 import android.view.View;
     28 import android.view.ViewGroup;
     29 import android.view.accessibility.AccessibilityEvent;
     30 import android.widget.ImageView;
     31 import android.widget.LinearLayout;
     32 import android.widget.Switch;
     33 import android.widget.TextView;
     34 
     35 import com.android.internal.logging.MetricsLogger;
     36 import com.android.systemui.FontSizeUtils;
     37 import com.android.systemui.R;
     38 import com.android.systemui.qs.QSTile.DetailAdapter;
     39 import com.android.systemui.statusbar.phone.BaseStatusBarHeader;
     40 import com.android.systemui.statusbar.phone.QSTileHost;
     41 
     42 public class QSDetail extends LinearLayout {
     43 
     44     private static final String TAG = "QSDetail";
     45     private static final long FADE_DURATION = 300;
     46 
     47     private final SparseArray<View> mDetailViews = new SparseArray<>();
     48 
     49     private ViewGroup mDetailContent;
     50     private TextView mDetailSettingsButton;
     51     private TextView mDetailDoneButton;
     52     private QSDetailClipper mClipper;
     53     private DetailAdapter mDetailAdapter;
     54     private QSPanel mQsPanel;
     55 
     56     private View mQsDetailHeader;
     57     private TextView mQsDetailHeaderTitle;
     58     private Switch mQsDetailHeaderSwitch;
     59     private ImageView mQsDetailHeaderProgress;
     60 
     61     private QSTileHost mHost;
     62 
     63     private boolean mScanState;
     64     private boolean mClosingDetail;
     65     private boolean mFullyExpanded;
     66     private BaseStatusBarHeader mHeader;
     67     private boolean mTriggeredExpand;
     68     private int mOpenX;
     69     private int mOpenY;
     70     private boolean mAnimatingOpen;
     71     private boolean mSwitchState;
     72 
     73     public QSDetail(Context context, @Nullable AttributeSet attrs) {
     74         super(context, attrs);
     75     }
     76 
     77     @Override
     78     protected void onConfigurationChanged(Configuration newConfig) {
     79         super.onConfigurationChanged(newConfig);
     80         FontSizeUtils.updateFontSize(mDetailDoneButton, R.dimen.qs_detail_button_text_size);
     81         FontSizeUtils.updateFontSize(mDetailSettingsButton, R.dimen.qs_detail_button_text_size);
     82 
     83         for (int i = 0; i < mDetailViews.size(); i++) {
     84             mDetailViews.valueAt(i).dispatchConfigurationChanged(newConfig);
     85         }
     86     }
     87 
     88     @Override
     89     protected void onFinishInflate() {
     90         super.onFinishInflate();
     91         mDetailContent = (ViewGroup) findViewById(android.R.id.content);
     92         mDetailSettingsButton = (TextView) findViewById(android.R.id.button2);
     93         mDetailDoneButton = (TextView) findViewById(android.R.id.button1);
     94 
     95         mQsDetailHeader = findViewById(R.id.qs_detail_header);
     96         mQsDetailHeaderTitle = (TextView) mQsDetailHeader.findViewById(android.R.id.title);
     97         mQsDetailHeaderSwitch = (Switch) mQsDetailHeader.findViewById(android.R.id.toggle);
     98         mQsDetailHeaderProgress = (ImageView) findViewById(R.id.qs_detail_header_progress);
     99 
    100         updateDetailText();
    101 
    102         mClipper = new QSDetailClipper(this);
    103 
    104         final OnClickListener doneListener = new OnClickListener() {
    105             @Override
    106             public void onClick(View v) {
    107                 announceForAccessibility(
    108                         mContext.getString(R.string.accessibility_desc_quick_settings));
    109                 mQsPanel.closeDetail();
    110             }
    111         };
    112         mDetailDoneButton.setOnClickListener(doneListener);
    113     }
    114 
    115     public void setQsPanel(QSPanel panel, BaseStatusBarHeader header) {
    116         mQsPanel = panel;
    117         mHeader = header;
    118         mHeader.setCallback(mQsPanelCallback);
    119         mQsPanel.setCallback(mQsPanelCallback);
    120     }
    121 
    122     public void setHost(QSTileHost host) {
    123         mHost = host;
    124     }
    125     public boolean isShowingDetail() {
    126         return mDetailAdapter != null;
    127     }
    128 
    129     public void setFullyExpanded(boolean fullyExpanded) {
    130         mFullyExpanded = fullyExpanded;
    131     }
    132 
    133     public void setExpanded(boolean qsExpanded) {
    134         if (!qsExpanded) {
    135             mTriggeredExpand = false;
    136         }
    137     }
    138 
    139     private void updateDetailText() {
    140         mDetailDoneButton.setText(R.string.quick_settings_done);
    141         mDetailSettingsButton.setText(R.string.quick_settings_more_settings);
    142     }
    143 
    144     public void updateResources() {
    145         updateDetailText();
    146     }
    147 
    148     public boolean isClosingDetail() {
    149         return mClosingDetail;
    150     }
    151 
    152     private void handleShowingDetail(final QSTile.DetailAdapter adapter, int x, int y) {
    153         final boolean showingDetail = adapter != null;
    154         setClickable(showingDetail);
    155         if (showingDetail) {
    156             mQsDetailHeaderTitle.setText(adapter.getTitle());
    157             final Boolean toggleState = adapter.getToggleState();
    158             if (toggleState == null) {
    159                 mQsDetailHeaderSwitch.setVisibility(INVISIBLE);
    160                 mQsDetailHeader.setClickable(false);
    161             } else {
    162                 mQsDetailHeaderSwitch.setVisibility(VISIBLE);
    163                 handleToggleStateChanged(toggleState, adapter.getToggleEnabled());
    164                 mQsDetailHeader.setClickable(true);
    165                 mQsDetailHeader.setOnClickListener(new OnClickListener() {
    166                     @Override
    167                     public void onClick(View v) {
    168                         boolean checked = !mQsDetailHeaderSwitch.isChecked();
    169                         mQsDetailHeaderSwitch.setChecked(checked);
    170                         adapter.setToggleState(checked);
    171                     }
    172                 });
    173             }
    174             if (!mFullyExpanded) {
    175                 mTriggeredExpand = true;
    176                 mHost.animateToggleQSExpansion();
    177             } else {
    178                 mTriggeredExpand = false;
    179             }
    180             mOpenX = x;
    181             mOpenY = y;
    182         } else {
    183             // Ensure we collapse into the same point we opened from.
    184             x = mOpenX;
    185             y = mOpenY;
    186             if (mTriggeredExpand) {
    187                 mHost.animateToggleQSExpansion();
    188                 mTriggeredExpand = false;
    189             }
    190         }
    191 
    192         boolean visibleDiff = (mDetailAdapter != null) != (adapter != null);
    193         if (!visibleDiff && mDetailAdapter == adapter) return;  // already in right state
    194         AnimatorListener listener = null;
    195         if (adapter != null) {
    196             int viewCacheIndex = adapter.getMetricsCategory();
    197             View detailView = adapter.createDetailView(mContext, mDetailViews.get(viewCacheIndex),
    198                     mDetailContent);
    199             if (detailView == null) throw new IllegalStateException("Must return detail view");
    200 
    201             final Intent settingsIntent = adapter.getSettingsIntent();
    202             mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
    203             mDetailSettingsButton.setOnClickListener(new OnClickListener() {
    204                 @Override
    205                 public void onClick(View v) {
    206                     mHost.startActivityDismissingKeyguard(settingsIntent);
    207                 }
    208             });
    209 
    210             mDetailContent.removeAllViews();
    211             mDetailContent.addView(detailView);
    212             mDetailViews.put(viewCacheIndex, detailView);
    213             MetricsLogger.visible(mContext, adapter.getMetricsCategory());
    214             announceForAccessibility(mContext.getString(
    215                     R.string.accessibility_quick_settings_detail,
    216                     adapter.getTitle()));
    217             mDetailAdapter = adapter;
    218             listener = mHideGridContentWhenDone;
    219             setVisibility(View.VISIBLE);
    220         } else {
    221             if (mDetailAdapter != null) {
    222                 MetricsLogger.hidden(mContext, mDetailAdapter.getMetricsCategory());
    223             }
    224             mClosingDetail = true;
    225             mDetailAdapter = null;
    226             listener = mTeardownDetailWhenDone;
    227             mHeader.setVisibility(View.VISIBLE);
    228             mQsPanel.setGridContentVisibility(true);
    229             mQsPanelCallback.onScanStateChanged(false);
    230         }
    231         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
    232         if (visibleDiff) {
    233             mAnimatingOpen = adapter != null;
    234             if (mFullyExpanded || mDetailAdapter != null) {
    235                 setAlpha(1);
    236                 mClipper.animateCircularClip(x, y, mDetailAdapter != null, listener);
    237             } else {
    238                 animate().alpha(0)
    239                         .setDuration(FADE_DURATION)
    240                         .setListener(listener)
    241                         .start();
    242             }
    243         }
    244     }
    245 
    246     private void handleToggleStateChanged(boolean state, boolean toggleEnabled) {
    247         mSwitchState = state;
    248         if (mAnimatingOpen) {
    249             return;
    250         }
    251         mQsDetailHeaderSwitch.setChecked(state);
    252         mQsDetailHeader.setEnabled(toggleEnabled);
    253         mQsDetailHeaderSwitch.setEnabled(toggleEnabled);
    254     }
    255 
    256     private void handleScanStateChanged(boolean state) {
    257         if (mScanState == state) return;
    258         mScanState = state;
    259         final Animatable anim = (Animatable) mQsDetailHeaderProgress.getDrawable();
    260         if (state) {
    261             mQsDetailHeaderProgress.animate().alpha(1f);
    262             anim.start();
    263         } else {
    264             mQsDetailHeaderProgress.animate().alpha(0f);
    265             anim.stop();
    266         }
    267     }
    268 
    269     private void checkPendingAnimations() {
    270         handleToggleStateChanged(mSwitchState,
    271                             mDetailAdapter != null && mDetailAdapter.getToggleEnabled());
    272     }
    273 
    274     private final QSPanel.Callback mQsPanelCallback = new QSPanel.Callback() {
    275         @Override
    276         public void onToggleStateChanged(final boolean state) {
    277             post(new Runnable() {
    278                 @Override
    279                 public void run() {
    280                     handleToggleStateChanged(state,
    281                             mDetailAdapter != null && mDetailAdapter.getToggleEnabled());
    282                 }
    283             });
    284         }
    285 
    286         @Override
    287         public void onShowingDetail(final DetailAdapter detail, final int x, final int y) {
    288             post(new Runnable() {
    289                 @Override
    290                 public void run() {
    291                     handleShowingDetail(detail, x, y);
    292                 }
    293             });
    294         }
    295 
    296         @Override
    297         public void onScanStateChanged(final boolean state) {
    298             post(new Runnable() {
    299                 @Override
    300                 public void run() {
    301                     handleScanStateChanged(state);
    302                 }
    303             });
    304         }
    305     };
    306 
    307     private final AnimatorListenerAdapter mHideGridContentWhenDone = new AnimatorListenerAdapter() {
    308         public void onAnimationCancel(Animator animation) {
    309             // If we have been cancelled, remove the listener so that onAnimationEnd doesn't get
    310             // called, this will avoid accidentally turning off the grid when we don't want to.
    311             animation.removeListener(this);
    312             mAnimatingOpen = false;
    313             checkPendingAnimations();
    314         };
    315 
    316         @Override
    317         public void onAnimationEnd(Animator animation) {
    318             // Only hide content if still in detail state.
    319             if (mDetailAdapter != null) {
    320                 mQsPanel.setGridContentVisibility(false);
    321                 mHeader.setVisibility(View.INVISIBLE);
    322             }
    323             mAnimatingOpen = false;
    324             checkPendingAnimations();
    325         }
    326     };
    327 
    328     private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() {
    329         public void onAnimationEnd(Animator animation) {
    330             mDetailContent.removeAllViews();
    331             setVisibility(View.INVISIBLE);
    332             mClosingDetail = false;
    333         };
    334     };
    335 }
    336