• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
QSDetail(Context context, @Nullable AttributeSet attrs)73     public QSDetail(Context context, @Nullable AttributeSet attrs) {
74         super(context, attrs);
75     }
76 
77     @Override
onConfigurationChanged(Configuration newConfig)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
onFinishInflate()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 
setQsPanel(QSPanel panel, BaseStatusBarHeader header)115     public void setQsPanel(QSPanel panel, BaseStatusBarHeader header) {
116         mQsPanel = panel;
117         mHeader = header;
118         mHeader.setCallback(mQsPanelCallback);
119         mQsPanel.setCallback(mQsPanelCallback);
120     }
121 
setHost(QSTileHost host)122     public void setHost(QSTileHost host) {
123         mHost = host;
124     }
isShowingDetail()125     public boolean isShowingDetail() {
126         return mDetailAdapter != null;
127     }
128 
setFullyExpanded(boolean fullyExpanded)129     public void setFullyExpanded(boolean fullyExpanded) {
130         mFullyExpanded = fullyExpanded;
131     }
132 
setExpanded(boolean qsExpanded)133     public void setExpanded(boolean qsExpanded) {
134         if (!qsExpanded) {
135             mTriggeredExpand = false;
136         }
137     }
138 
updateDetailText()139     private void updateDetailText() {
140         mDetailDoneButton.setText(R.string.quick_settings_done);
141         mDetailSettingsButton.setText(R.string.quick_settings_more_settings);
142     }
143 
updateResources()144     public void updateResources() {
145         updateDetailText();
146     }
147 
isClosingDetail()148     public boolean isClosingDetail() {
149         return mClosingDetail;
150     }
151 
handleShowingDetail(final QSTile.DetailAdapter adapter, int x, int y)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 
handleToggleStateChanged(boolean state, boolean toggleEnabled)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 
handleScanStateChanged(boolean state)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 
checkPendingAnimations()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