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 static com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState;
20 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
21 
22 import android.annotation.Nullable;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.res.Configuration;
26 import android.content.res.Resources;
27 import android.metrics.LogMaker;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.Message;
31 import android.service.quicksettings.Tile;
32 import android.util.AttributeSet;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.widget.LinearLayout;
36 
37 import com.android.internal.logging.MetricsLogger;
38 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
39 import com.android.settingslib.Utils;
40 import com.android.systemui.Dependency;
41 import com.android.systemui.DumpController;
42 import com.android.systemui.Dumpable;
43 import com.android.systemui.R;
44 import com.android.systemui.plugins.qs.DetailAdapter;
45 import com.android.systemui.plugins.qs.QSTile;
46 import com.android.systemui.plugins.qs.QSTileView;
47 import com.android.systemui.qs.QSHost.Callback;
48 import com.android.systemui.qs.customize.QSCustomizer;
49 import com.android.systemui.qs.external.CustomTile;
50 import com.android.systemui.settings.BrightnessController;
51 import com.android.systemui.settings.ToggleSliderView;
52 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
53 import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener;
54 import com.android.systemui.tuner.TunerService;
55 import com.android.systemui.tuner.TunerService.Tunable;
56 
57 import java.io.FileDescriptor;
58 import java.io.PrintWriter;
59 import java.util.ArrayList;
60 import java.util.Collection;
61 
62 import javax.inject.Inject;
63 import javax.inject.Named;
64 
65 /** View that represents the quick settings tile panel (when expanded/pulled down). **/
66 public class QSPanel extends LinearLayout implements Tunable, Callback, BrightnessMirrorListener,
67         Dumpable {
68 
69     public static final String QS_SHOW_BRIGHTNESS = "qs_show_brightness";
70     public static final String QS_SHOW_HEADER = "qs_show_header";
71 
72     private static final String TAG = "QSPanel";
73 
74     protected final Context mContext;
75     protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
76     protected final View mBrightnessView;
77     private final H mHandler = new H();
78     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
79     private final QSTileRevealController mQsTileRevealController;
80 
81     protected boolean mExpanded;
82     protected boolean mListening;
83 
84     private QSDetail.Callback mCallback;
85     private BrightnessController mBrightnessController;
86     private DumpController mDumpController;
87     protected QSTileHost mHost;
88 
89     protected QSSecurityFooter mFooter;
90     private PageIndicator mFooterPageIndicator;
91     private boolean mGridContentVisible = true;
92 
93     protected QSTileLayout mTileLayout;
94 
95     private QSCustomizer mCustomizePanel;
96     private Record mDetailRecord;
97 
98     private BrightnessMirrorController mBrightnessMirrorController;
99     private View mDivider;
100 
QSPanel(Context context)101     public QSPanel(Context context) {
102         this(context, null);
103     }
104 
QSPanel(Context context, AttributeSet attrs)105     public QSPanel(Context context, AttributeSet attrs) {
106         this(context, attrs, null);
107     }
108 
109     @Inject
QSPanel(@amedVIEW_CONTEXT) Context context, AttributeSet attrs, DumpController dumpController)110     public QSPanel(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
111             DumpController dumpController) {
112         super(context, attrs);
113         mContext = context;
114 
115         setOrientation(VERTICAL);
116 
117         mBrightnessView = LayoutInflater.from(mContext).inflate(
118             R.layout.quick_settings_brightness_dialog, this, false);
119         addView(mBrightnessView);
120 
121         mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate(
122                 R.layout.qs_paged_tile_layout, this, false);
123         mTileLayout.setListening(mListening);
124         addView((View) mTileLayout);
125 
126         mQsTileRevealController = new QSTileRevealController(mContext, this,
127                 (PagedTileLayout) mTileLayout);
128 
129         addDivider();
130 
131         mFooter = new QSSecurityFooter(this, context);
132         addView(mFooter.getView());
133 
134         updateResources();
135 
136         mBrightnessController = new BrightnessController(getContext(),
137                 findViewById(R.id.brightness_slider));
138         mDumpController = dumpController;
139     }
140 
addDivider()141     protected void addDivider() {
142         mDivider = LayoutInflater.from(mContext).inflate(R.layout.qs_divider, this, false);
143         mDivider.setBackgroundColor(Utils.applyAlpha(mDivider.getAlpha(),
144                 getColorForState(mContext, Tile.STATE_ACTIVE)));
145         addView(mDivider);
146     }
147 
148     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)149     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
150         // We want all the logic of LinearLayout#onMeasure, and for it to assign the excess space
151         // not used by the other children to PagedTileLayout. However, in this case, LinearLayout
152         // assumes that PagedTileLayout would use all the excess space. This is not the case as
153         // PagedTileLayout height is quantized (because it shows a certain number of rows).
154         // Therefore, after everything is measured, we need to make sure that we add up the correct
155         // total height
156         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
157         int height = getPaddingBottom() + getPaddingTop();
158         int numChildren = getChildCount();
159         for (int i = 0; i < numChildren; i++) {
160             View child = getChildAt(i);
161             if (child.getVisibility() != View.GONE) height += child.getMeasuredHeight();
162         }
163         setMeasuredDimension(getMeasuredWidth(), height);
164     }
165 
getDivider()166     public View getDivider() {
167         return mDivider;
168     }
169 
getQsTileRevealController()170     public QSTileRevealController getQsTileRevealController() {
171         return mQsTileRevealController;
172     }
173 
isShowingCustomize()174     public boolean isShowingCustomize() {
175         return mCustomizePanel != null && mCustomizePanel.isCustomizing();
176     }
177 
178     @Override
onAttachedToWindow()179     protected void onAttachedToWindow() {
180         super.onAttachedToWindow();
181         final TunerService tunerService = Dependency.get(TunerService.class);
182         tunerService.addTunable(this, QS_SHOW_BRIGHTNESS);
183 
184         if (mHost != null) {
185             setTiles(mHost.getTiles());
186         }
187         if (mBrightnessMirrorController != null) {
188             mBrightnessMirrorController.addCallback(this);
189         }
190         if (mDumpController != null) mDumpController.addListener(this);
191     }
192 
193     @Override
onDetachedFromWindow()194     protected void onDetachedFromWindow() {
195         Dependency.get(TunerService.class).removeTunable(this);
196         if (mHost != null) {
197             mHost.removeCallback(this);
198         }
199         for (TileRecord record : mRecords) {
200             record.tile.removeCallbacks();
201         }
202         if (mBrightnessMirrorController != null) {
203             mBrightnessMirrorController.removeCallback(this);
204         }
205         if (mDumpController != null) mDumpController.removeListener(this);
206         super.onDetachedFromWindow();
207     }
208 
209     @Override
onTilesChanged()210     public void onTilesChanged() {
211         setTiles(mHost.getTiles());
212     }
213 
214     @Override
onTuningChanged(String key, String newValue)215     public void onTuningChanged(String key, String newValue) {
216         if (QS_SHOW_BRIGHTNESS.equals(key)) {
217             updateViewVisibilityForTuningValue(mBrightnessView, newValue);
218         }
219     }
220 
updateViewVisibilityForTuningValue(View view, @Nullable String newValue)221     private void updateViewVisibilityForTuningValue(View view, @Nullable String newValue) {
222         view.setVisibility(TunerService.parseIntegerSwitch(newValue, true) ? VISIBLE : GONE);
223     }
224 
openDetails(String subPanel)225     public void openDetails(String subPanel) {
226         QSTile tile = getTile(subPanel);
227         // If there's no tile with that name (as defined in QSFactoryImpl or other QSFactory),
228         // QSFactory will not be able to create a tile and getTile will return null
229         if (tile != null) {
230             showDetailAdapter(true, tile.getDetailAdapter(), new int[]{getWidth() / 2, 0});
231         }
232     }
233 
getTile(String subPanel)234     private QSTile getTile(String subPanel) {
235         for (int i = 0; i < mRecords.size(); i++) {
236             if (subPanel.equals(mRecords.get(i).tile.getTileSpec())) {
237                 return mRecords.get(i).tile;
238             }
239         }
240         return mHost.createTile(subPanel);
241     }
242 
setBrightnessMirror(BrightnessMirrorController c)243     public void setBrightnessMirror(BrightnessMirrorController c) {
244         if (mBrightnessMirrorController != null) {
245             mBrightnessMirrorController.removeCallback(this);
246         }
247         mBrightnessMirrorController = c;
248         if (mBrightnessMirrorController != null) {
249             mBrightnessMirrorController.addCallback(this);
250         }
251         updateBrightnessMirror();
252     }
253 
254     @Override
onBrightnessMirrorReinflated(View brightnessMirror)255     public void onBrightnessMirrorReinflated(View brightnessMirror) {
256         updateBrightnessMirror();
257     }
258 
getBrightnessView()259     View getBrightnessView() {
260         return mBrightnessView;
261     }
262 
setCallback(QSDetail.Callback callback)263     public void setCallback(QSDetail.Callback callback) {
264         mCallback = callback;
265     }
266 
setHost(QSTileHost host, QSCustomizer customizer)267     public void setHost(QSTileHost host, QSCustomizer customizer) {
268         mHost = host;
269         mHost.addCallback(this);
270         setTiles(mHost.getTiles());
271         mFooter.setHostEnvironment(host);
272         mCustomizePanel = customizer;
273         if (mCustomizePanel != null) {
274             mCustomizePanel.setHost(mHost);
275         }
276     }
277 
278     /**
279      * Links the footer's page indicator, which is used in landscape orientation to save space.
280      *
281      * @param pageIndicator indicator to use for page scrolling
282      */
setFooterPageIndicator(PageIndicator pageIndicator)283     public void setFooterPageIndicator(PageIndicator pageIndicator) {
284         if (mTileLayout instanceof PagedTileLayout) {
285             mFooterPageIndicator = pageIndicator;
286             updatePageIndicator();
287         }
288     }
289 
updatePageIndicator()290     private void updatePageIndicator() {
291         if (mTileLayout instanceof PagedTileLayout) {
292             if (mFooterPageIndicator != null) {
293                 mFooterPageIndicator.setVisibility(View.GONE);
294 
295                 ((PagedTileLayout) mTileLayout).setPageIndicator(mFooterPageIndicator);
296             }
297         }
298     }
299 
getHost()300     public QSTileHost getHost() {
301         return mHost;
302     }
303 
updateResources()304     public void updateResources() {
305         final Resources res = mContext.getResources();
306         setPadding(0, res.getDimensionPixelSize(R.dimen.qs_panel_padding_top), 0, res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom));
307 
308         updatePageIndicator();
309 
310         if (mListening) {
311             refreshAllTiles();
312         }
313         if (mTileLayout != null) {
314             mTileLayout.updateResources();
315         }
316     }
317 
318     @Override
onConfigurationChanged(Configuration newConfig)319     protected void onConfigurationChanged(Configuration newConfig) {
320         super.onConfigurationChanged(newConfig);
321         mFooter.onConfigurationChanged();
322         updateResources();
323 
324         updateBrightnessMirror();
325     }
326 
updateBrightnessMirror()327     public void updateBrightnessMirror() {
328         if (mBrightnessMirrorController != null) {
329             ToggleSliderView brightnessSlider = findViewById(R.id.brightness_slider);
330             ToggleSliderView mirrorSlider = mBrightnessMirrorController.getMirror()
331                     .findViewById(R.id.brightness_slider);
332             brightnessSlider.setMirror(mirrorSlider);
333             brightnessSlider.setMirrorController(mBrightnessMirrorController);
334         }
335     }
336 
onCollapse()337     public void onCollapse() {
338         if (mCustomizePanel != null && mCustomizePanel.isShown()) {
339             mCustomizePanel.hide();
340         }
341     }
342 
setExpanded(boolean expanded)343     public void setExpanded(boolean expanded) {
344         if (mExpanded == expanded) return;
345         mExpanded = expanded;
346         if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
347             ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
348         }
349         mMetricsLogger.visibility(MetricsEvent.QS_PANEL, mExpanded);
350         if (!mExpanded) {
351             closeDetail();
352         } else {
353             logTiles();
354         }
355     }
356 
setPageListener(final PagedTileLayout.PageListener pageListener)357     public void setPageListener(final PagedTileLayout.PageListener pageListener) {
358         if (mTileLayout instanceof PagedTileLayout) {
359             ((PagedTileLayout) mTileLayout).setPageListener(pageListener);
360         }
361     }
362 
isExpanded()363     public boolean isExpanded() {
364         return mExpanded;
365     }
366 
setListening(boolean listening)367     public void setListening(boolean listening) {
368         if (mListening == listening) return;
369         mListening = listening;
370         if (mTileLayout != null) {
371             mTileLayout.setListening(listening);
372         }
373         if (mListening) {
374             refreshAllTiles();
375         }
376     }
377 
setListening(boolean listening, boolean expanded)378     public void setListening(boolean listening, boolean expanded) {
379         setListening(listening && expanded);
380         getFooter().setListening(listening);
381         // Set the listening as soon as the QS fragment starts listening regardless of the expansion,
382         // so it will update the current brightness before the slider is visible.
383         setBrightnessListening(listening);
384     }
385 
setBrightnessListening(boolean listening)386     public void setBrightnessListening(boolean listening) {
387         if (listening) {
388             mBrightnessController.registerCallbacks();
389         } else {
390             mBrightnessController.unregisterCallbacks();
391         }
392     }
393 
refreshAllTiles()394     public void refreshAllTiles() {
395         mBrightnessController.checkRestrictionAndSetEnabled();
396         for (TileRecord r : mRecords) {
397             r.tile.refreshState();
398         }
399         mFooter.refreshState();
400     }
401 
showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow)402     public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) {
403         int xInWindow = locationInWindow[0];
404         int yInWindow = locationInWindow[1];
405         ((View) getParent()).getLocationInWindow(locationInWindow);
406 
407         Record r = new Record();
408         r.detailAdapter = adapter;
409         r.x = xInWindow - locationInWindow[0];
410         r.y = yInWindow - locationInWindow[1];
411 
412         locationInWindow[0] = xInWindow;
413         locationInWindow[1] = yInWindow;
414 
415         showDetail(show, r);
416     }
417 
showDetail(boolean show, Record r)418     protected void showDetail(boolean show, Record r) {
419         mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
420     }
421 
setTiles(Collection<QSTile> tiles)422     public void setTiles(Collection<QSTile> tiles) {
423         setTiles(tiles, false);
424     }
425 
setTiles(Collection<QSTile> tiles, boolean collapsedView)426     public void setTiles(Collection<QSTile> tiles, boolean collapsedView) {
427         if (!collapsedView) {
428             mQsTileRevealController.updateRevealedTiles(tiles);
429         }
430         for (TileRecord record : mRecords) {
431             mTileLayout.removeTile(record);
432             record.tile.removeCallback(record.callback);
433         }
434         mRecords.clear();
435         for (QSTile tile : tiles) {
436             addTile(tile, collapsedView);
437         }
438     }
439 
drawTile(TileRecord r, QSTile.State state)440     protected void drawTile(TileRecord r, QSTile.State state) {
441         r.tileView.onStateChanged(state);
442     }
443 
createTileView(QSTile tile, boolean collapsedView)444     protected QSTileView createTileView(QSTile tile, boolean collapsedView) {
445         return mHost.createTileView(tile, collapsedView);
446     }
447 
shouldShowDetail()448     protected boolean shouldShowDetail() {
449         return mExpanded;
450     }
451 
addTile(final QSTile tile, boolean collapsedView)452     protected TileRecord addTile(final QSTile tile, boolean collapsedView) {
453         final TileRecord r = new TileRecord();
454         r.tile = tile;
455         r.tileView = createTileView(tile, collapsedView);
456         final QSTile.Callback callback = new QSTile.Callback() {
457             @Override
458             public void onStateChanged(QSTile.State state) {
459                 drawTile(r, state);
460             }
461 
462             @Override
463             public void onShowDetail(boolean show) {
464                 // Both the collapsed and full QS panels get this callback, this check determines
465                 // which one should handle showing the detail.
466                 if (shouldShowDetail()) {
467                     QSPanel.this.showDetail(show, r);
468                 }
469             }
470 
471             @Override
472             public void onToggleStateChanged(boolean state) {
473                 if (mDetailRecord == r) {
474                     fireToggleStateChanged(state);
475                 }
476             }
477 
478             @Override
479             public void onScanStateChanged(boolean state) {
480                 r.scanState = state;
481                 if (mDetailRecord == r) {
482                     fireScanStateChanged(r.scanState);
483                 }
484             }
485 
486             @Override
487             public void onAnnouncementRequested(CharSequence announcement) {
488                 if (announcement != null) {
489                     mHandler.obtainMessage(H.ANNOUNCE_FOR_ACCESSIBILITY, announcement)
490                             .sendToTarget();
491                 }
492             }
493         };
494         r.tile.addCallback(callback);
495         r.callback = callback;
496         r.tileView.init(r.tile);
497         r.tile.refreshState();
498         mRecords.add(r);
499 
500         if (mTileLayout != null) {
501             mTileLayout.addTile(r);
502         }
503 
504         return r;
505     }
506 
507 
showEdit(final View v)508     public void showEdit(final View v) {
509         v.post(new Runnable() {
510             @Override
511             public void run() {
512                 if (mCustomizePanel != null) {
513                     if (!mCustomizePanel.isCustomizing()) {
514                         int[] loc = v.getLocationOnScreen();
515                         int x = loc[0] + v.getWidth() / 2;
516                         int y = loc[1] + v.getHeight() / 2;
517                         mCustomizePanel.show(x, y);
518                     }
519                 }
520 
521             }
522         });
523     }
524 
closeDetail()525     public void closeDetail() {
526         if (mCustomizePanel != null && mCustomizePanel.isShown()) {
527             // Treat this as a detail panel for now, to make things easy.
528             mCustomizePanel.hide();
529             return;
530         }
531         showDetail(false, mDetailRecord);
532     }
533 
getGridHeight()534     public int getGridHeight() {
535         return getMeasuredHeight();
536     }
537 
handleShowDetail(Record r, boolean show)538     protected void handleShowDetail(Record r, boolean show) {
539         if (r instanceof TileRecord) {
540             handleShowDetailTile((TileRecord) r, show);
541         } else {
542             int x = 0;
543             int y = 0;
544             if (r != null) {
545                 x = r.x;
546                 y = r.y;
547             }
548             handleShowDetailImpl(r, show, x, y);
549         }
550     }
551 
handleShowDetailTile(TileRecord r, boolean show)552     private void handleShowDetailTile(TileRecord r, boolean show) {
553         if ((mDetailRecord != null) == show && mDetailRecord == r) return;
554 
555         if (show) {
556             r.detailAdapter = r.tile.getDetailAdapter();
557             if (r.detailAdapter == null) return;
558         }
559         r.tile.setDetailListening(show);
560         int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
561         int y = r.tileView.getDetailY() + mTileLayout.getOffsetTop(r) + getTop();
562         handleShowDetailImpl(r, show, x, y);
563     }
564 
handleShowDetailImpl(Record r, boolean show, int x, int y)565     private void handleShowDetailImpl(Record r, boolean show, int x, int y) {
566         setDetailRecord(show ? r : null);
567         fireShowingDetail(show ? r.detailAdapter : null, x, y);
568     }
569 
setDetailRecord(Record r)570     protected void setDetailRecord(Record r) {
571         if (r == mDetailRecord) return;
572         mDetailRecord = r;
573         final boolean scanState = mDetailRecord instanceof TileRecord
574                 && ((TileRecord) mDetailRecord).scanState;
575         fireScanStateChanged(scanState);
576     }
577 
setGridContentVisibility(boolean visible)578     void setGridContentVisibility(boolean visible) {
579         int newVis = visible ? VISIBLE : INVISIBLE;
580         setVisibility(newVis);
581         if (mGridContentVisible != visible) {
582             mMetricsLogger.visibility(MetricsEvent.QS_PANEL, newVis);
583         }
584         mGridContentVisible = visible;
585     }
586 
logTiles()587     private void logTiles() {
588         for (int i = 0; i < mRecords.size(); i++) {
589             QSTile tile = mRecords.get(i).tile;
590             mMetricsLogger.write(tile.populate(new LogMaker(tile.getMetricsCategory())
591                     .setType(MetricsEvent.TYPE_OPEN)));
592         }
593     }
594 
fireShowingDetail(DetailAdapter detail, int x, int y)595     private void fireShowingDetail(DetailAdapter detail, int x, int y) {
596         if (mCallback != null) {
597             mCallback.onShowingDetail(detail, x, y);
598         }
599     }
600 
fireToggleStateChanged(boolean state)601     private void fireToggleStateChanged(boolean state) {
602         if (mCallback != null) {
603             mCallback.onToggleStateChanged(state);
604         }
605     }
606 
fireScanStateChanged(boolean state)607     private void fireScanStateChanged(boolean state) {
608         if (mCallback != null) {
609             mCallback.onScanStateChanged(state);
610         }
611     }
612 
clickTile(ComponentName tile)613     public void clickTile(ComponentName tile) {
614         final String spec = CustomTile.toSpec(tile);
615         final int N = mRecords.size();
616         for (int i = 0; i < N; i++) {
617             if (mRecords.get(i).tile.getTileSpec().equals(spec)) {
618                 mRecords.get(i).tile.click();
619                 break;
620             }
621         }
622     }
623 
getTileLayout()624     QSTileLayout getTileLayout() {
625         return mTileLayout;
626     }
627 
getTileView(QSTile tile)628     QSTileView getTileView(QSTile tile) {
629         for (TileRecord r : mRecords) {
630             if (r.tile == tile) {
631                 return r.tileView;
632             }
633         }
634         return null;
635     }
636 
getFooter()637     public QSSecurityFooter getFooter() {
638         return mFooter;
639     }
640 
showDeviceMonitoringDialog()641     public void showDeviceMonitoringDialog() {
642         mFooter.showDeviceMonitoringDialog();
643     }
644 
setMargins(int sideMargins)645     public void setMargins(int sideMargins) {
646         for (int i = 0; i < getChildCount(); i++) {
647             View view = getChildAt(i);
648             if (view != mTileLayout) {
649                 LayoutParams lp = (LayoutParams) view.getLayoutParams();
650                 lp.leftMargin = sideMargins;
651                 lp.rightMargin = sideMargins;
652             }
653         }
654     }
655 
656     private class H extends Handler {
657         private static final int SHOW_DETAIL = 1;
658         private static final int SET_TILE_VISIBILITY = 2;
659         private static final int ANNOUNCE_FOR_ACCESSIBILITY = 3;
660 
661         @Override
handleMessage(Message msg)662         public void handleMessage(Message msg) {
663             if (msg.what == SHOW_DETAIL) {
664                 handleShowDetail((Record) msg.obj, msg.arg1 != 0);
665             } else if (msg.what == ANNOUNCE_FOR_ACCESSIBILITY) {
666                 announceForAccessibility((CharSequence) msg.obj);
667             }
668         }
669     }
670 
671     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)672     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
673         pw.println(getClass().getSimpleName() + ":");
674         pw.println("  Tile records:");
675         for (TileRecord record : mRecords) {
676             if (record.tile instanceof Dumpable) {
677                 pw.print("    "); ((Dumpable) record.tile).dump(fd, pw, args);
678                 pw.print("    "); pw.println(record.tileView.toString());
679             }
680         }
681     }
682 
683     protected static class Record {
684         DetailAdapter detailAdapter;
685         int x;
686         int y;
687     }
688 
689     public static final class TileRecord extends Record {
690         public QSTile tile;
691         public com.android.systemui.plugins.qs.QSTileView tileView;
692         public boolean scanState;
693         public QSTile.Callback callback;
694     }
695 
696     public interface QSTileLayout {
697 
saveInstanceState(Bundle outState)698         default void saveInstanceState(Bundle outState) {}
699 
restoreInstanceState(Bundle savedInstanceState)700         default void restoreInstanceState(Bundle savedInstanceState) {}
701 
addTile(TileRecord tile)702         void addTile(TileRecord tile);
703 
removeTile(TileRecord tile)704         void removeTile(TileRecord tile);
705 
getOffsetTop(TileRecord tile)706         int getOffsetTop(TileRecord tile);
707 
updateResources()708         boolean updateResources();
709 
setListening(boolean listening)710         void setListening(boolean listening);
711 
setExpansion(float expansion)712         default void setExpansion(float expansion) {}
713 
getNumVisibleTiles()714         int getNumVisibleTiles();
715     }
716 }
717