Home | History | Annotate | Download | only in qs
      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 
    101     public QSPanel(Context context) {
    102         this(context, null);
    103     }
    104 
    105     public QSPanel(Context context, AttributeSet attrs) {
    106         this(context, attrs, null);
    107     }
    108 
    109     @Inject
    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 
    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
    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 
    166     public View getDivider() {
    167         return mDivider;
    168     }
    169 
    170     public QSTileRevealController getQsTileRevealController() {
    171         return mQsTileRevealController;
    172     }
    173 
    174     public boolean isShowingCustomize() {
    175         return mCustomizePanel != null && mCustomizePanel.isCustomizing();
    176     }
    177 
    178     @Override
    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
    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
    210     public void onTilesChanged() {
    211         setTiles(mHost.getTiles());
    212     }
    213 
    214     @Override
    215     public void onTuningChanged(String key, String newValue) {
    216         if (QS_SHOW_BRIGHTNESS.equals(key)) {
    217             updateViewVisibilityForTuningValue(mBrightnessView, newValue);
    218         }
    219     }
    220 
    221     private void updateViewVisibilityForTuningValue(View view, @Nullable String newValue) {
    222         view.setVisibility(TunerService.parseIntegerSwitch(newValue, true) ? VISIBLE : GONE);
    223     }
    224 
    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 
    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 
    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
    255     public void onBrightnessMirrorReinflated(View brightnessMirror) {
    256         updateBrightnessMirror();
    257     }
    258 
    259     View getBrightnessView() {
    260         return mBrightnessView;
    261     }
    262 
    263     public void setCallback(QSDetail.Callback callback) {
    264         mCallback = callback;
    265     }
    266 
    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      */
    283     public void setFooterPageIndicator(PageIndicator pageIndicator) {
    284         if (mTileLayout instanceof PagedTileLayout) {
    285             mFooterPageIndicator = pageIndicator;
    286             updatePageIndicator();
    287         }
    288     }
    289 
    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 
    300     public QSTileHost getHost() {
    301         return mHost;
    302     }
    303 
    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
    319     protected void onConfigurationChanged(Configuration newConfig) {
    320         super.onConfigurationChanged(newConfig);
    321         mFooter.onConfigurationChanged();
    322         updateResources();
    323 
    324         updateBrightnessMirror();
    325     }
    326 
    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 
    337     public void onCollapse() {
    338         if (mCustomizePanel != null && mCustomizePanel.isShown()) {
    339             mCustomizePanel.hide();
    340         }
    341     }
    342 
    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 
    357     public void setPageListener(final PagedTileLayout.PageListener pageListener) {
    358         if (mTileLayout instanceof PagedTileLayout) {
    359             ((PagedTileLayout) mTileLayout).setPageListener(pageListener);
    360         }
    361     }
    362 
    363     public boolean isExpanded() {
    364         return mExpanded;
    365     }
    366 
    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 
    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 
    386     public void setBrightnessListening(boolean listening) {
    387         if (listening) {
    388             mBrightnessController.registerCallbacks();
    389         } else {
    390             mBrightnessController.unregisterCallbacks();
    391         }
    392     }
    393 
    394     public void refreshAllTiles() {
    395         mBrightnessController.checkRestrictionAndSetEnabled();
    396         for (TileRecord r : mRecords) {
    397             r.tile.refreshState();
    398         }
    399         mFooter.refreshState();
    400     }
    401 
    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 
    418     protected void showDetail(boolean show, Record r) {
    419         mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
    420     }
    421 
    422     public void setTiles(Collection<QSTile> tiles) {
    423         setTiles(tiles, false);
    424     }
    425 
    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 
    440     protected void drawTile(TileRecord r, QSTile.State state) {
    441         r.tileView.onStateChanged(state);
    442     }
    443 
    444     protected QSTileView createTileView(QSTile tile, boolean collapsedView) {
    445         return mHost.createTileView(tile, collapsedView);
    446     }
    447 
    448     protected boolean shouldShowDetail() {
    449         return mExpanded;
    450     }
    451 
    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 
    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 
    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 
    534     public int getGridHeight() {
    535         return getMeasuredHeight();
    536     }
    537 
    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 
    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 
    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 
    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 
    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 
    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 
    595     private void fireShowingDetail(DetailAdapter detail, int x, int y) {
    596         if (mCallback != null) {
    597             mCallback.onShowingDetail(detail, x, y);
    598         }
    599     }
    600 
    601     private void fireToggleStateChanged(boolean state) {
    602         if (mCallback != null) {
    603             mCallback.onToggleStateChanged(state);
    604         }
    605     }
    606 
    607     private void fireScanStateChanged(boolean state) {
    608         if (mCallback != null) {
    609             mCallback.onScanStateChanged(state);
    610         }
    611     }
    612 
    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 
    624     QSTileLayout getTileLayout() {
    625         return mTileLayout;
    626     }
    627 
    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 
    637     public QSSecurityFooter getFooter() {
    638         return mFooter;
    639     }
    640 
    641     public void showDeviceMonitoringDialog() {
    642         mFooter.showDeviceMonitoringDialog();
    643     }
    644 
    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
    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
    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 
    698         default void saveInstanceState(Bundle outState) {}
    699 
    700         default void restoreInstanceState(Bundle savedInstanceState) {}
    701 
    702         void addTile(TileRecord tile);
    703 
    704         void removeTile(TileRecord tile);
    705 
    706         int getOffsetTop(TileRecord tile);
    707 
    708         boolean updateResources();
    709 
    710         void setListening(boolean listening);
    711 
    712         default void setExpansion(float expansion) {}
    713 
    714         int getNumVisibleTiles();
    715     }
    716 }
    717