1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.qs;
18 
19 import android.animation.Animator;
20 import android.animation.Animator.AnimatorListener;
21 import android.animation.AnimatorListenerAdapter;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.res.Configuration;
25 import android.content.res.Resources;
26 import android.os.Handler;
27 import android.os.Message;
28 import android.util.AttributeSet;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.view.accessibility.AccessibilityEvent;
33 import android.widget.ImageView;
34 import android.widget.TextView;
35 
36 import com.android.internal.logging.MetricsLogger;
37 import com.android.systemui.FontSizeUtils;
38 import com.android.systemui.R;
39 import com.android.systemui.qs.QSTile.DetailAdapter;
40 import com.android.systemui.settings.BrightnessController;
41 import com.android.systemui.settings.ToggleSlider;
42 import com.android.systemui.statusbar.phone.QSTileHost;
43 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
44 
45 import java.util.ArrayList;
46 import java.util.Collection;
47 
48 /** View that represents the quick settings tile panel. **/
49 public class QSPanel extends ViewGroup {
50     private static final float TILE_ASPECT = 1.2f;
51 
52     private final Context mContext;
53     protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
54     private final View mDetail;
55     private final ViewGroup mDetailContent;
56     private final TextView mDetailSettingsButton;
57     private final TextView mDetailDoneButton;
58     protected final View mBrightnessView;
59     private final QSDetailClipper mClipper;
60     private final H mHandler = new H();
61 
62     private int mColumns;
63     private int mCellWidth;
64     private int mCellHeight;
65     private int mLargeCellWidth;
66     private int mLargeCellHeight;
67     private int mPanelPaddingBottom;
68     private int mDualTileUnderlap;
69     private int mBrightnessPaddingTop;
70     private int mGridHeight;
71     private boolean mExpanded;
72     private boolean mListening;
73     private boolean mClosingDetail;
74 
75     private Record mDetailRecord;
76     private Callback mCallback;
77     private BrightnessController mBrightnessController;
78     private QSTileHost mHost;
79 
80     private QSFooter mFooter;
81     private boolean mGridContentVisible = true;
82 
QSPanel(Context context)83     public QSPanel(Context context) {
84         this(context, null);
85     }
86 
QSPanel(Context context, AttributeSet attrs)87     public QSPanel(Context context, AttributeSet attrs) {
88         super(context, attrs);
89         mContext = context;
90 
91         mDetail = LayoutInflater.from(context).inflate(R.layout.qs_detail, this, false);
92         mDetailContent = (ViewGroup) mDetail.findViewById(android.R.id.content);
93         mDetailSettingsButton = (TextView) mDetail.findViewById(android.R.id.button2);
94         mDetailDoneButton = (TextView) mDetail.findViewById(android.R.id.button1);
95         updateDetailText();
96         mDetail.setVisibility(GONE);
97         mDetail.setClickable(true);
98         mBrightnessView = LayoutInflater.from(context).inflate(
99                 R.layout.quick_settings_brightness_dialog, this, false);
100         mFooter = new QSFooter(this, context);
101         addView(mDetail);
102         addView(mBrightnessView);
103         addView(mFooter.getView());
104         mClipper = new QSDetailClipper(mDetail);
105         updateResources();
106 
107         mBrightnessController = new BrightnessController(getContext(),
108                 (ImageView) findViewById(R.id.brightness_icon),
109                 (ToggleSlider) findViewById(R.id.brightness_slider));
110 
111         mDetailDoneButton.setOnClickListener(new OnClickListener() {
112             @Override
113             public void onClick(View v) {
114                 announceForAccessibility(
115                         mContext.getString(R.string.accessibility_desc_quick_settings));
116                 closeDetail();
117             }
118         });
119     }
120 
updateDetailText()121     private void updateDetailText() {
122         mDetailDoneButton.setText(R.string.quick_settings_done);
123         mDetailSettingsButton.setText(R.string.quick_settings_more_settings);
124     }
125 
setBrightnessMirror(BrightnessMirrorController c)126     public void setBrightnessMirror(BrightnessMirrorController c) {
127         super.onFinishInflate();
128         ToggleSlider brightnessSlider = (ToggleSlider) findViewById(R.id.brightness_slider);
129         ToggleSlider mirror = (ToggleSlider) c.getMirror().findViewById(R.id.brightness_slider);
130         brightnessSlider.setMirror(mirror);
131         brightnessSlider.setMirrorController(c);
132     }
133 
setCallback(Callback callback)134     public void setCallback(Callback callback) {
135         mCallback = callback;
136     }
137 
setHost(QSTileHost host)138     public void setHost(QSTileHost host) {
139         mHost = host;
140         mFooter.setHost(host);
141     }
142 
getHost()143     public QSTileHost getHost() {
144         return mHost;
145     }
146 
updateResources()147     public void updateResources() {
148         final Resources res = mContext.getResources();
149         final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
150         mCellHeight = res.getDimensionPixelSize(R.dimen.qs_tile_height);
151         mCellWidth = (int)(mCellHeight * TILE_ASPECT);
152         mLargeCellHeight = res.getDimensionPixelSize(R.dimen.qs_dual_tile_height);
153         mLargeCellWidth = (int)(mLargeCellHeight * TILE_ASPECT);
154         mPanelPaddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
155         mDualTileUnderlap = res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_vertical);
156         mBrightnessPaddingTop = res.getDimensionPixelSize(R.dimen.qs_brightness_padding_top);
157         if (mColumns != columns) {
158             mColumns = columns;
159             postInvalidate();
160         }
161         for (TileRecord r : mRecords) {
162             r.tile.clearState();
163         }
164         if (mListening) {
165             refreshAllTiles();
166         }
167         updateDetailText();
168     }
169 
170     @Override
onConfigurationChanged(Configuration newConfig)171     protected void onConfigurationChanged(Configuration newConfig) {
172         super.onConfigurationChanged(newConfig);
173         FontSizeUtils.updateFontSize(mDetailDoneButton, R.dimen.qs_detail_button_text_size);
174         FontSizeUtils.updateFontSize(mDetailSettingsButton, R.dimen.qs_detail_button_text_size);
175 
176         // We need to poke the detail views as well as they might not be attached to the view
177         // hierarchy but reused at a later point.
178         int count = mRecords.size();
179         for (int i = 0; i < count; i++) {
180             View detailView = mRecords.get(i).detailView;
181             if (detailView != null) {
182                 detailView.dispatchConfigurationChanged(newConfig);
183             }
184         }
185         mFooter.onConfigurationChanged();
186     }
187 
setExpanded(boolean expanded)188     public void setExpanded(boolean expanded) {
189         if (mExpanded == expanded) return;
190         mExpanded = expanded;
191         MetricsLogger.visibility(mContext, MetricsLogger.QS_PANEL, mExpanded);
192         if (!mExpanded) {
193             closeDetail();
194         } else {
195             logTiles();
196         }
197     }
198 
setListening(boolean listening)199     public void setListening(boolean listening) {
200         if (mListening == listening) return;
201         mListening = listening;
202         for (TileRecord r : mRecords) {
203             r.tile.setListening(mListening);
204         }
205         mFooter.setListening(mListening);
206         if (mListening) {
207             refreshAllTiles();
208         }
209         if (listening) {
210             mBrightnessController.registerCallbacks();
211         } else {
212             mBrightnessController.unregisterCallbacks();
213         }
214     }
215 
refreshAllTiles()216     public void refreshAllTiles() {
217         for (TileRecord r : mRecords) {
218             r.tile.refreshState();
219         }
220         mFooter.refreshState();
221     }
222 
showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow)223     public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) {
224         int xInWindow = locationInWindow[0];
225         int yInWindow = locationInWindow[1];
226         mDetail.getLocationInWindow(locationInWindow);
227 
228         Record r = new Record();
229         r.detailAdapter = adapter;
230         r.x = xInWindow - locationInWindow[0];
231         r.y = yInWindow - locationInWindow[1];
232 
233         locationInWindow[0] = xInWindow;
234         locationInWindow[1] = yInWindow;
235 
236         showDetail(show, r);
237     }
238 
showDetail(boolean show, Record r)239     private void showDetail(boolean show, Record r) {
240         mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
241     }
242 
setTileVisibility(View v, int visibility)243     private void setTileVisibility(View v, int visibility) {
244         mHandler.obtainMessage(H.SET_TILE_VISIBILITY, visibility, 0, v).sendToTarget();
245     }
246 
handleSetTileVisibility(View v, int visibility)247     private void handleSetTileVisibility(View v, int visibility) {
248         if (visibility == VISIBLE && !mGridContentVisible) {
249             visibility = INVISIBLE;
250         }
251         if (visibility == v.getVisibility()) return;
252         v.setVisibility(visibility);
253     }
254 
setTiles(Collection<QSTile<?>> tiles)255     public void setTiles(Collection<QSTile<?>> tiles) {
256         for (TileRecord record : mRecords) {
257             removeView(record.tileView);
258         }
259         mRecords.clear();
260         for (QSTile<?> tile : tiles) {
261             addTile(tile);
262         }
263         if (isShowingDetail()) {
264             mDetail.bringToFront();
265         }
266     }
267 
drawTile(TileRecord r, QSTile.State state)268     private void drawTile(TileRecord r, QSTile.State state) {
269         final int visibility = state.visible ? VISIBLE : GONE;
270         setTileVisibility(r.tileView, visibility);
271         r.tileView.onStateChanged(state);
272     }
273 
addTile(final QSTile<?> tile)274     private void addTile(final QSTile<?> tile) {
275         final TileRecord r = new TileRecord();
276         r.tile = tile;
277         r.tileView = tile.createTileView(mContext);
278         r.tileView.setVisibility(View.GONE);
279         final QSTile.Callback callback = new QSTile.Callback() {
280             @Override
281             public void onStateChanged(QSTile.State state) {
282                 if (!r.openingDetail) {
283                     drawTile(r, state);
284                 }
285             }
286             @Override
287             public void onShowDetail(boolean show) {
288                 QSPanel.this.showDetail(show, r);
289             }
290             @Override
291             public void onToggleStateChanged(boolean state) {
292                 if (mDetailRecord == r) {
293                     fireToggleStateChanged(state);
294                 }
295             }
296             @Override
297             public void onScanStateChanged(boolean state) {
298                 r.scanState = state;
299                 if (mDetailRecord == r) {
300                     fireScanStateChanged(r.scanState);
301                 }
302             }
303 
304             @Override
305             public void onAnnouncementRequested(CharSequence announcement) {
306                 announceForAccessibility(announcement);
307             }
308         };
309         r.tile.setCallback(callback);
310         final View.OnClickListener click = new View.OnClickListener() {
311             @Override
312             public void onClick(View v) {
313                 r.tile.click();
314             }
315         };
316         final View.OnClickListener clickSecondary = new View.OnClickListener() {
317             @Override
318             public void onClick(View v) {
319                 r.tile.secondaryClick();
320             }
321         };
322         final View.OnLongClickListener longClick = new View.OnLongClickListener() {
323             @Override
324             public boolean onLongClick(View v) {
325                 r.tile.longClick();
326                 return true;
327             }
328         };
329         r.tileView.init(click, clickSecondary, longClick);
330         r.tile.setListening(mListening);
331         callback.onStateChanged(r.tile.getState());
332         r.tile.refreshState();
333         mRecords.add(r);
334 
335         addView(r.tileView);
336     }
337 
isShowingDetail()338     public boolean isShowingDetail() {
339         return mDetailRecord != null;
340     }
341 
closeDetail()342     public void closeDetail() {
343         showDetail(false, mDetailRecord);
344     }
345 
isClosingDetail()346     public boolean isClosingDetail() {
347         return mClosingDetail;
348     }
349 
getGridHeight()350     public int getGridHeight() {
351         return mGridHeight;
352     }
353 
handleShowDetail(Record r, boolean show)354     private void handleShowDetail(Record r, boolean show) {
355         if (r instanceof TileRecord) {
356             handleShowDetailTile((TileRecord) r, show);
357         } else {
358             int x = 0;
359             int y = 0;
360             if (r != null) {
361                 x = r.x;
362                 y = r.y;
363             }
364             handleShowDetailImpl(r, show, x, y);
365         }
366     }
367 
handleShowDetailTile(TileRecord r, boolean show)368     private void handleShowDetailTile(TileRecord r, boolean show) {
369         if ((mDetailRecord != null) == show && mDetailRecord == r) return;
370 
371         if (show) {
372             r.detailAdapter = r.tile.getDetailAdapter();
373             if (r.detailAdapter == null) return;
374         }
375         r.tile.setDetailListening(show);
376         int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
377         int y = r.tileView.getTop() + r.tileView.getHeight() / 2;
378         handleShowDetailImpl(r, show, x, y);
379     }
380 
handleShowDetailImpl(Record r, boolean show, int x, int y)381     private void handleShowDetailImpl(Record r, boolean show, int x, int y) {
382         boolean visibleDiff = (mDetailRecord != null) != show;
383         if (!visibleDiff && mDetailRecord == r) return;  // already in right state
384         DetailAdapter detailAdapter = null;
385         AnimatorListener listener = null;
386         if (show) {
387             detailAdapter = r.detailAdapter;
388             r.detailView = detailAdapter.createDetailView(mContext, r.detailView, mDetailContent);
389             if (r.detailView == null) throw new IllegalStateException("Must return detail view");
390 
391             final Intent settingsIntent = detailAdapter.getSettingsIntent();
392             mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
393             mDetailSettingsButton.setOnClickListener(new OnClickListener() {
394                 @Override
395                 public void onClick(View v) {
396                     mHost.startActivityDismissingKeyguard(settingsIntent);
397                 }
398             });
399 
400             mDetailContent.removeAllViews();
401             mDetail.bringToFront();
402             mDetailContent.addView(r.detailView);
403             MetricsLogger.visible(mContext, detailAdapter.getMetricsCategory());
404             announceForAccessibility(mContext.getString(
405                     R.string.accessibility_quick_settings_detail,
406                     mContext.getString(detailAdapter.getTitle())));
407             setDetailRecord(r);
408             listener = mHideGridContentWhenDone;
409             if (r instanceof TileRecord && visibleDiff) {
410                 ((TileRecord) r).openingDetail = true;
411             }
412         } else {
413             if (mDetailRecord != null) {
414                 MetricsLogger.hidden(mContext, mDetailRecord.detailAdapter.getMetricsCategory());
415             }
416             mClosingDetail = true;
417             setGridContentVisibility(true);
418             listener = mTeardownDetailWhenDone;
419             fireScanStateChanged(false);
420         }
421         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
422         fireShowingDetail(show ? detailAdapter : null);
423         if (visibleDiff) {
424             mClipper.animateCircularClip(x, y, show, listener);
425         }
426     }
427 
setGridContentVisibility(boolean visible)428     private void setGridContentVisibility(boolean visible) {
429         int newVis = visible ? VISIBLE : INVISIBLE;
430         for (int i = 0; i < mRecords.size(); i++) {
431             TileRecord tileRecord = mRecords.get(i);
432             if (tileRecord.tileView.getVisibility() != GONE) {
433                 tileRecord.tileView.setVisibility(newVis);
434             }
435         }
436         mBrightnessView.setVisibility(newVis);
437         if (mGridContentVisible != visible) {
438             MetricsLogger.visibility(mContext, MetricsLogger.QS_PANEL, newVis);
439         }
440         mGridContentVisible = visible;
441     }
442 
logTiles()443     private void logTiles() {
444         for (int i = 0; i < mRecords.size(); i++) {
445             TileRecord tileRecord = mRecords.get(i);
446             if (tileRecord.tile.getState().visible) {
447                 MetricsLogger.visible(mContext, tileRecord.tile.getMetricsCategory());
448             }
449         }
450     }
451 
452     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)453     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
454         final int width = MeasureSpec.getSize(widthMeasureSpec);
455         mBrightnessView.measure(exactly(width), MeasureSpec.UNSPECIFIED);
456         final int brightnessHeight = mBrightnessView.getMeasuredHeight() + mBrightnessPaddingTop;
457         mFooter.getView().measure(exactly(width), MeasureSpec.UNSPECIFIED);
458         int r = -1;
459         int c = -1;
460         int rows = 0;
461         boolean rowIsDual = false;
462         for (TileRecord record : mRecords) {
463             if (record.tileView.getVisibility() == GONE) continue;
464             // wrap to next column if we've reached the max # of columns
465             // also don't allow dual + single tiles on the same row
466             if (r == -1 || c == (mColumns - 1) || rowIsDual != record.tile.supportsDualTargets()) {
467                 r++;
468                 c = 0;
469                 rowIsDual = record.tile.supportsDualTargets();
470             } else {
471                 c++;
472             }
473             record.row = r;
474             record.col = c;
475             rows = r + 1;
476         }
477 
478         View previousView = mBrightnessView;
479         for (TileRecord record : mRecords) {
480             if (record.tileView.setDual(record.tile.supportsDualTargets())) {
481                 record.tileView.handleStateChanged(record.tile.getState());
482             }
483             if (record.tileView.getVisibility() == GONE) continue;
484             final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
485             final int ch = record.row == 0 ? mLargeCellHeight : mCellHeight;
486             record.tileView.measure(exactly(cw), exactly(ch));
487             previousView = record.tileView.updateAccessibilityOrder(previousView);
488         }
489         int h = rows == 0 ? brightnessHeight : (getRowTop(rows) + mPanelPaddingBottom);
490         if (mFooter.hasFooter()) {
491             h += mFooter.getView().getMeasuredHeight();
492         }
493         mDetail.measure(exactly(width), MeasureSpec.UNSPECIFIED);
494         if (mDetail.getMeasuredHeight() < h) {
495             mDetail.measure(exactly(width), exactly(h));
496         }
497         mGridHeight = h;
498         setMeasuredDimension(width, Math.max(h, mDetail.getMeasuredHeight()));
499     }
500 
exactly(int size)501     private static int exactly(int size) {
502         return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
503     }
504 
505     @Override
onLayout(boolean changed, int l, int t, int r, int b)506     protected void onLayout(boolean changed, int l, int t, int r, int b) {
507         final int w = getWidth();
508         mBrightnessView.layout(0, mBrightnessPaddingTop,
509                 mBrightnessView.getMeasuredWidth(),
510                 mBrightnessPaddingTop + mBrightnessView.getMeasuredHeight());
511         boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
512         for (TileRecord record : mRecords) {
513             if (record.tileView.getVisibility() == GONE) continue;
514             final int cols = getColumnCount(record.row);
515             final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
516             final int extra = (w - cw * cols) / (cols + 1);
517             int left = record.col * cw + (record.col + 1) * extra;
518             final int top = getRowTop(record.row);
519             int right;
520             int tileWith = record.tileView.getMeasuredWidth();
521             if (isRtl) {
522                 right = w - left;
523                 left = right - tileWith;
524             } else {
525                 right = left + tileWith;
526             }
527             record.tileView.layout(left, top, right, top + record.tileView.getMeasuredHeight());
528         }
529         final int dh = Math.max(mDetail.getMeasuredHeight(), getMeasuredHeight());
530         mDetail.layout(0, 0, mDetail.getMeasuredWidth(), dh);
531         if (mFooter.hasFooter()) {
532             View footer = mFooter.getView();
533             footer.layout(0, getMeasuredHeight() - footer.getMeasuredHeight(),
534                     footer.getMeasuredWidth(), getMeasuredHeight());
535         }
536     }
537 
getRowTop(int row)538     private int getRowTop(int row) {
539         if (row <= 0) return mBrightnessView.getMeasuredHeight() + mBrightnessPaddingTop;
540         return mBrightnessView.getMeasuredHeight() + mBrightnessPaddingTop
541                 + mLargeCellHeight - mDualTileUnderlap + (row - 1) * mCellHeight;
542     }
543 
getColumnCount(int row)544     private int getColumnCount(int row) {
545         int cols = 0;
546         for (TileRecord record : mRecords) {
547             if (record.tileView.getVisibility() == GONE) continue;
548             if (record.row == row) cols++;
549         }
550         return cols;
551     }
552 
fireShowingDetail(QSTile.DetailAdapter detail)553     private void fireShowingDetail(QSTile.DetailAdapter detail) {
554         if (mCallback != null) {
555             mCallback.onShowingDetail(detail);
556         }
557     }
558 
fireToggleStateChanged(boolean state)559     private void fireToggleStateChanged(boolean state) {
560         if (mCallback != null) {
561             mCallback.onToggleStateChanged(state);
562         }
563     }
564 
fireScanStateChanged(boolean state)565     private void fireScanStateChanged(boolean state) {
566         if (mCallback != null) {
567             mCallback.onScanStateChanged(state);
568         }
569     }
570 
setDetailRecord(Record r)571     private void setDetailRecord(Record r) {
572         if (r == mDetailRecord) return;
573         mDetailRecord = r;
574         final boolean scanState = mDetailRecord instanceof TileRecord
575                 && ((TileRecord) mDetailRecord).scanState;
576         fireScanStateChanged(scanState);
577     }
578 
579     private class H extends Handler {
580         private static final int SHOW_DETAIL = 1;
581         private static final int SET_TILE_VISIBILITY = 2;
582         @Override
handleMessage(Message msg)583         public void handleMessage(Message msg) {
584             if (msg.what == SHOW_DETAIL) {
585                 handleShowDetail((Record)msg.obj, msg.arg1 != 0);
586             } else if (msg.what == SET_TILE_VISIBILITY) {
587                 handleSetTileVisibility((View)msg.obj, msg.arg1);
588             }
589         }
590     }
591 
592     private static class Record {
593         View detailView;
594         DetailAdapter detailAdapter;
595         int x;
596         int y;
597     }
598 
599     protected static final class TileRecord extends Record {
600         public QSTile<?> tile;
601         public QSTileView tileView;
602         public int row;
603         public int col;
604         public boolean scanState;
605         public boolean openingDetail;
606     }
607 
608     private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() {
609         public void onAnimationEnd(Animator animation) {
610             mDetailContent.removeAllViews();
611             setDetailRecord(null);
612             mClosingDetail = false;
613         };
614     };
615 
616     private final AnimatorListenerAdapter mHideGridContentWhenDone = new AnimatorListenerAdapter() {
617         public void onAnimationCancel(Animator animation) {
618             // If we have been cancelled, remove the listener so that onAnimationEnd doesn't get
619             // called, this will avoid accidentally turning off the grid when we don't want to.
620             animation.removeListener(this);
621             redrawTile();
622         };
623 
624         @Override
625         public void onAnimationEnd(Animator animation) {
626             // Only hide content if still in detail state.
627             if (mDetailRecord != null) {
628                 setGridContentVisibility(false);
629                 redrawTile();
630             }
631         }
632 
633         private void redrawTile() {
634             if (mDetailRecord instanceof TileRecord) {
635                 final TileRecord tileRecord = (TileRecord) mDetailRecord;
636                 tileRecord.openingDetail = false;
637                 drawTile(tileRecord, tileRecord.tile.getState());
638             }
639         }
640     };
641 
642     public interface Callback {
onShowingDetail(QSTile.DetailAdapter detail)643         void onShowingDetail(QSTile.DetailAdapter detail);
onToggleStateChanged(boolean state)644         void onToggleStateChanged(boolean state);
onScanStateChanged(boolean state)645         void onScanStateChanged(boolean state);
646     }
647 }
648