• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.util.Utils.useQsMediaPlayer;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.content.res.Configuration;
25 import android.content.res.Resources;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.Message;
29 import android.util.AttributeSet;
30 import android.util.Log;
31 import android.view.Gravity;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.widget.LinearLayout;
36 
37 import androidx.annotation.VisibleForTesting;
38 
39 import com.android.internal.logging.UiEventLogger;
40 import com.android.internal.widget.RemeasuringLinearLayout;
41 import com.android.systemui.R;
42 import com.android.systemui.plugins.qs.DetailAdapter;
43 import com.android.systemui.plugins.qs.QSTile;
44 import com.android.systemui.settings.brightness.BrightnessSlider;
45 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
46 import com.android.systemui.tuner.TunerService;
47 import com.android.systemui.tuner.TunerService.Tunable;
48 import com.android.systemui.util.animation.UniqueObjectHostView;
49 
50 import java.util.ArrayList;
51 import java.util.List;
52 
53 /** View that represents the quick settings tile panel (when expanded/pulled down). **/
54 public class QSPanel extends LinearLayout implements Tunable {
55 
56     public static final String QS_SHOW_BRIGHTNESS = "qs_show_brightness";
57     public static final String QS_SHOW_HEADER = "qs_show_header";
58 
59     private static final String TAG = "QSPanel";
60 
61     protected final Context mContext;
62     private final int mMediaTopMargin;
63     private final int mMediaTotalBottomMargin;
64 
65     /**
66      * The index where the content starts that needs to be moved between parents
67      */
68     private int mMovableContentStartIndex;
69 
70     @Nullable
71     protected View mBrightnessView;
72     @Nullable
73     protected BrightnessSlider mToggleSliderController;
74 
75     private final H mHandler = new H();
76     /** Whether or not the QS media player feature is enabled. */
77     protected boolean mUsingMediaPlayer;
78 
79     protected boolean mExpanded;
80     protected boolean mListening;
81 
82     private QSDetail.Callback mCallback;
83     protected QSTileHost mHost;
84     private final List<OnConfigurationChangedListener> mOnConfigurationChangedListeners =
85             new ArrayList<>();
86 
87     @Nullable
88     protected View mSecurityFooter;
89 
90     @Nullable
91     protected View mFooter;
92 
93     @Nullable
94     private ViewGroup mHeaderContainer;
95     private PageIndicator mFooterPageIndicator;
96     private int mContentMarginStart;
97     private int mContentMarginEnd;
98     private boolean mUsingHorizontalLayout;
99 
100     private Record mDetailRecord;
101 
102     private BrightnessMirrorController mBrightnessMirrorController;
103     private LinearLayout mHorizontalLinearLayout;
104     protected LinearLayout mHorizontalContentContainer;
105 
106     protected QSTileLayout mTileLayout;
107 
QSPanel(Context context, AttributeSet attrs)108     public QSPanel(Context context, AttributeSet attrs) {
109         super(context, attrs);
110         mUsingMediaPlayer = useQsMediaPlayer(context);
111         mMediaTotalBottomMargin = getResources().getDimensionPixelSize(
112                 R.dimen.quick_settings_bottom_margin_media);
113         mMediaTopMargin = getResources().getDimensionPixelSize(
114                 R.dimen.qs_tile_margin_vertical);
115         mContext = context;
116 
117         setOrientation(VERTICAL);
118 
119         mMovableContentStartIndex = getChildCount();
120 
121     }
122 
initialize()123     void initialize() {
124         mTileLayout = getOrCreateTileLayout();
125 
126         if (mUsingMediaPlayer) {
127             mHorizontalLinearLayout = new RemeasuringLinearLayout(mContext);
128             mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
129             mHorizontalLinearLayout.setClipChildren(false);
130             mHorizontalLinearLayout.setClipToPadding(false);
131 
132             mHorizontalContentContainer = new RemeasuringLinearLayout(mContext);
133             mHorizontalContentContainer.setOrientation(LinearLayout.VERTICAL);
134             mHorizontalContentContainer.setClipChildren(true);
135             mHorizontalContentContainer.setClipToPadding(false);
136 
137             LayoutParams lp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1);
138             int marginSize = (int) mContext.getResources().getDimension(R.dimen.qs_media_padding);
139             lp.setMarginStart(0);
140             lp.setMarginEnd(marginSize);
141             lp.gravity = Gravity.CENTER_VERTICAL;
142             mHorizontalLinearLayout.addView(mHorizontalContentContainer, lp);
143 
144             lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0, 1);
145             addView(mHorizontalLinearLayout, lp);
146         }
147     }
148 
149     /**
150      * Add brightness view above the tile layout.
151      *
152      * Used to add the brightness slider after construction.
153      */
setBrightnessView(@onNull View view)154     public void setBrightnessView(@NonNull View view) {
155         if (mBrightnessView != null) {
156             removeView(mBrightnessView);
157             mMovableContentStartIndex--;
158         }
159         addView(view, 0);
160         mBrightnessView = view;
161 
162         setBrightnessViewMargin();
163 
164         mMovableContentStartIndex++;
165     }
166 
setBrightnessViewMargin()167     private void setBrightnessViewMargin() {
168         if (mBrightnessView != null) {
169             MarginLayoutParams lp = (MarginLayoutParams) mBrightnessView.getLayoutParams();
170             lp.topMargin = mContext.getResources()
171                     .getDimensionPixelSize(R.dimen.qs_brightness_margin_top);
172             lp.bottomMargin = mContext.getResources()
173                     .getDimensionPixelSize(R.dimen.qs_brightness_margin_bottom);
174             mBrightnessView.setLayoutParams(lp);
175         }
176     }
177 
178     /** */
getOrCreateTileLayout()179     public QSTileLayout getOrCreateTileLayout() {
180         if (mTileLayout == null) {
181             mTileLayout = (QSTileLayout) LayoutInflater.from(mContext)
182                     .inflate(R.layout.qs_paged_tile_layout, this, false);
183         }
184         return mTileLayout;
185     }
186 
187     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)188     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
189         if (mTileLayout instanceof PagedTileLayout) {
190             // Since PageIndicator gets measured before PagedTileLayout, we preemptively set the
191             // # of pages before the measurement pass so PageIndicator is measured appropriately
192             if (mFooterPageIndicator != null) {
193                 mFooterPageIndicator.setNumPages(((PagedTileLayout) mTileLayout).getNumPages());
194             }
195 
196             // In landscape, mTileLayout's parent is not the panel but a view that contains the
197             // tile layout and the media controls.
198             if (((View) mTileLayout).getParent() == this) {
199                 // Allow the UI to be as big as it want's to, we're in a scroll view
200                 int newHeight = 10000;
201                 int availableHeight = MeasureSpec.getSize(heightMeasureSpec);
202                 int excessHeight = newHeight - availableHeight;
203                 // Measure with EXACTLY. That way, The content will only use excess height and will
204                 // be measured last, after other views and padding is accounted for. This only
205                 // works because our Layouts in here remeasure themselves with the exact content
206                 // height.
207                 heightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY);
208                 ((PagedTileLayout) mTileLayout).setExcessHeight(excessHeight);
209             }
210         }
211         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
212 
213         // We want all the logic of LinearLayout#onMeasure, and for it to assign the excess space
214         // not used by the other children to PagedTileLayout. However, in this case, LinearLayout
215         // assumes that PagedTileLayout would use all the excess space. This is not the case as
216         // PagedTileLayout height is quantized (because it shows a certain number of rows).
217         // Therefore, after everything is measured, we need to make sure that we add up the correct
218         // total height
219         int height = getPaddingBottom() + getPaddingTop();
220         int numChildren = getChildCount();
221         for (int i = 0; i < numChildren; i++) {
222             View child = getChildAt(i);
223             if (child.getVisibility() != View.GONE) {
224                 height += child.getMeasuredHeight();
225                 MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
226                 height += layoutParams.topMargin + layoutParams.bottomMargin;
227             }
228         }
229         setMeasuredDimension(getMeasuredWidth(), height);
230     }
231 
getDumpableTag()232     protected String getDumpableTag() {
233         return TAG;
234     }
235 
236     @Override
onTuningChanged(String key, String newValue)237     public void onTuningChanged(String key, String newValue) {
238         if (QS_SHOW_BRIGHTNESS.equals(key) && mBrightnessView != null) {
239             updateViewVisibilityForTuningValue(mBrightnessView, newValue);
240         }
241     }
242 
updateViewVisibilityForTuningValue(View view, @Nullable String newValue)243     private void updateViewVisibilityForTuningValue(View view, @Nullable String newValue) {
244         view.setVisibility(TunerService.parseIntegerSwitch(newValue, true) ? VISIBLE : GONE);
245     }
246 
247     /** */
openDetails(QSTile tile)248     public void openDetails(QSTile tile) {
249         // If there's no tile with that name (as defined in QSFactoryImpl or other QSFactory),
250         // QSFactory will not be able to create a tile and getTile will return null
251         if (tile != null) {
252             showDetailAdapter(true, tile.getDetailAdapter(), new int[]{getWidth() / 2, 0});
253         }
254     }
255 
256     @Nullable
getBrightnessView()257     View getBrightnessView() {
258         return mBrightnessView;
259     }
260 
setCallback(QSDetail.Callback callback)261     public void setCallback(QSDetail.Callback callback) {
262         mCallback = callback;
263     }
264 
265     /**
266      * Links the footer's page indicator, which is used in landscape orientation to save space.
267      *
268      * @param pageIndicator indicator to use for page scrolling
269      */
setFooterPageIndicator(PageIndicator pageIndicator)270     public void setFooterPageIndicator(PageIndicator pageIndicator) {
271         if (mTileLayout instanceof PagedTileLayout) {
272             mFooterPageIndicator = pageIndicator;
273             updatePageIndicator();
274         }
275     }
276 
updatePageIndicator()277     private void updatePageIndicator() {
278         if (mTileLayout instanceof PagedTileLayout) {
279             if (mFooterPageIndicator != null) {
280                 mFooterPageIndicator.setVisibility(View.GONE);
281 
282                 ((PagedTileLayout) mTileLayout).setPageIndicator(mFooterPageIndicator);
283             }
284         }
285     }
286 
getHost()287     public QSTileHost getHost() {
288         return mHost;
289     }
290 
updateResources()291     public void updateResources() {
292         updatePadding();
293 
294         updatePageIndicator();
295 
296         setBrightnessViewMargin();
297 
298         if (mTileLayout != null) {
299             mTileLayout.updateResources();
300         }
301     }
302 
updatePadding()303     protected void updatePadding() {
304         final Resources res = mContext.getResources();
305         int padding = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top);
306         setPaddingRelative(getPaddingStart(),
307                 padding,
308                 getPaddingEnd(),
309                 res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom));
310     }
311 
addOnConfigurationChangedListener(OnConfigurationChangedListener listener)312     void addOnConfigurationChangedListener(OnConfigurationChangedListener listener) {
313         mOnConfigurationChangedListeners.add(listener);
314     }
315 
removeOnConfigurationChangedListener(OnConfigurationChangedListener listener)316     void removeOnConfigurationChangedListener(OnConfigurationChangedListener listener) {
317         mOnConfigurationChangedListeners.remove(listener);
318     }
319 
320     @Override
onConfigurationChanged(Configuration newConfig)321     protected void onConfigurationChanged(Configuration newConfig) {
322         super.onConfigurationChanged(newConfig);
323         mOnConfigurationChangedListeners.forEach(
324                 listener -> listener.onConfigurationChange(newConfig));
325         switchSecurityFooter();
326     }
327 
328     @Override
onFinishInflate()329     protected void onFinishInflate() {
330         super.onFinishInflate();
331         mFooter = findViewById(R.id.qs_footer);
332     }
333 
updateHorizontalLinearLayoutMargins()334     private void updateHorizontalLinearLayoutMargins() {
335         if (mHorizontalLinearLayout != null && !displayMediaMarginsOnMedia()) {
336             LayoutParams lp = (LayoutParams) mHorizontalLinearLayout.getLayoutParams();
337             lp.bottomMargin = Math.max(mMediaTotalBottomMargin - getPaddingBottom(), 0);
338             mHorizontalLinearLayout.setLayoutParams(lp);
339         }
340     }
341 
342     /**
343      * @return true if the margin bottom of the media view should be on the media host or false
344      *         if they should be on the HorizontalLinearLayout. Returning {@code false} is useful
345      *         to visually center the tiles in the Media view, which doesn't work when the
346      *         expanded panel actually scrolls.
347      */
displayMediaMarginsOnMedia()348     protected boolean displayMediaMarginsOnMedia() {
349         return true;
350     }
351 
352     /**
353      * @return true if the media view needs margin on the top to separate it from the qs tiles
354      */
mediaNeedsTopMargin()355     protected boolean mediaNeedsTopMargin() {
356         return false;
357     }
358 
needsDynamicRowsAndColumns()359     private boolean needsDynamicRowsAndColumns() {
360         return true;
361     }
362 
switchAllContentToParent(ViewGroup parent, QSTileLayout newLayout)363     private void switchAllContentToParent(ViewGroup parent, QSTileLayout newLayout) {
364         int index = parent == this ? mMovableContentStartIndex : 0;
365 
366         // Let's first move the tileLayout to the new parent, since that should come first.
367         switchToParent((View) newLayout, parent, index);
368         index++;
369 
370         if (mFooter != null) {
371             // Then the footer with the settings
372             switchToParent(mFooter, parent, index);
373             index++;
374         }
375 
376         // The security footer is switched on orientation changes
377     }
378 
switchSecurityFooter()379     private void switchSecurityFooter() {
380         if (mSecurityFooter != null) {
381             if (mContext.getResources().getConfiguration().orientation
382                     == Configuration.ORIENTATION_LANDSCAPE && mHeaderContainer != null) {
383                 // Adding the security view to the header, that enables us to avoid scrolling
384                 switchToParent(mSecurityFooter, mHeaderContainer, 0);
385             } else {
386                 // Where should this go? If there's media, right before it. Otherwise, at the end.
387                 View mediaView = findViewByPredicate(v -> v instanceof UniqueObjectHostView);
388                 int index = -1;
389                 if (mediaView != null) {
390                     index = indexOfChild(mediaView);
391                 }
392                 if (mSecurityFooter.getParent() == this && indexOfChild(mSecurityFooter) < index) {
393                     // When we remove the securityFooter to rearrange, the index of media will go
394                     // down by one, so we correct it
395                     index--;
396                 }
397                 switchToParent(mSecurityFooter, this, index);
398             }
399         }
400     }
401 
switchToParent(View child, ViewGroup parent, int index)402     private void switchToParent(View child, ViewGroup parent, int index) {
403         switchToParent(child, parent, index, getDumpableTag());
404     }
405 
406     /** Call when orientation has changed and MediaHost needs to be adjusted. */
reAttachMediaHost(ViewGroup hostView, boolean horizontal)407     private void reAttachMediaHost(ViewGroup hostView, boolean horizontal) {
408         if (!mUsingMediaPlayer) {
409             return;
410         }
411         ViewGroup newParent = horizontal ? mHorizontalLinearLayout : this;
412         ViewGroup currentParent = (ViewGroup) hostView.getParent();
413         if (currentParent != newParent) {
414             if (currentParent != null) {
415                 currentParent.removeView(hostView);
416             }
417             newParent.addView(hostView);
418             LinearLayout.LayoutParams layoutParams = (LayoutParams) hostView.getLayoutParams();
419             layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
420             layoutParams.width = horizontal ? 0 : ViewGroup.LayoutParams.MATCH_PARENT;
421             layoutParams.weight = horizontal ? 1f : 0;
422             // Add any bottom margin, such that the total spacing is correct. This is only
423             // necessary if the view isn't horizontal, since otherwise the padding is
424             // carried in the parent of this view (to ensure correct vertical alignment)
425             layoutParams.bottomMargin = !horizontal || displayMediaMarginsOnMedia()
426                     ? Math.max(mMediaTotalBottomMargin - getPaddingBottom(), 0) : 0;
427             layoutParams.topMargin = mediaNeedsTopMargin() && !horizontal
428                     ? mMediaTopMargin : 0;
429         }
430     }
431 
setExpanded(boolean expanded)432     public void setExpanded(boolean expanded) {
433         if (mExpanded == expanded) return;
434         mExpanded = expanded;
435         if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
436             ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
437         }
438     }
439 
setPageListener(final PagedTileLayout.PageListener pageListener)440     public void setPageListener(final PagedTileLayout.PageListener pageListener) {
441         if (mTileLayout instanceof PagedTileLayout) {
442             ((PagedTileLayout) mTileLayout).setPageListener(pageListener);
443         }
444     }
445 
isExpanded()446     public boolean isExpanded() {
447         return mExpanded;
448     }
449 
450     /** */
setListening(boolean listening)451     public void setListening(boolean listening) {
452         mListening = listening;
453     }
454 
showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow)455     public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) {
456         int xInWindow = locationInWindow[0];
457         int yInWindow = locationInWindow[1];
458         ((View) getParent()).getLocationInWindow(locationInWindow);
459 
460         Record r = new Record();
461         r.detailAdapter = adapter;
462         r.x = xInWindow - locationInWindow[0];
463         r.y = yInWindow - locationInWindow[1];
464 
465         locationInWindow[0] = xInWindow;
466         locationInWindow[1] = yInWindow;
467 
468         showDetail(show, r);
469     }
470 
showDetail(boolean show, Record r)471     protected void showDetail(boolean show, Record r) {
472         mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
473     }
474 
drawTile(QSPanelControllerBase.TileRecord r, QSTile.State state)475     protected void drawTile(QSPanelControllerBase.TileRecord r, QSTile.State state) {
476         r.tileView.onStateChanged(state);
477     }
478 
openPanelEvent()479     protected QSEvent openPanelEvent() {
480         return QSEvent.QS_PANEL_EXPANDED;
481     }
482 
closePanelEvent()483     protected QSEvent closePanelEvent() {
484         return QSEvent.QS_PANEL_COLLAPSED;
485     }
486 
tileVisibleEvent()487     protected QSEvent tileVisibleEvent() {
488         return QSEvent.QS_TILE_VISIBLE;
489     }
490 
shouldShowDetail()491     protected boolean shouldShowDetail() {
492         return mExpanded;
493     }
494 
addTile(QSPanelControllerBase.TileRecord tileRecord)495     void addTile(QSPanelControllerBase.TileRecord tileRecord) {
496         final QSTile.Callback callback = new QSTile.Callback() {
497             @Override
498             public void onStateChanged(QSTile.State state) {
499                 drawTile(tileRecord, state);
500             }
501 
502             @Override
503             public void onShowDetail(boolean show) {
504                 // Both the collapsed and full QS panels get this callback, this check determines
505                 // which one should handle showing the detail.
506                 if (shouldShowDetail()) {
507                     QSPanel.this.showDetail(show, tileRecord);
508                 }
509             }
510 
511             @Override
512             public void onToggleStateChanged(boolean state) {
513                 if (mDetailRecord == tileRecord) {
514                     fireToggleStateChanged(state);
515                 }
516             }
517 
518             @Override
519             public void onScanStateChanged(boolean state) {
520                 tileRecord.scanState = state;
521                 if (mDetailRecord == tileRecord) {
522                     fireScanStateChanged(tileRecord.scanState);
523                 }
524             }
525 
526             @Override
527             public void onAnnouncementRequested(CharSequence announcement) {
528                 if (announcement != null) {
529                     mHandler.obtainMessage(H.ANNOUNCE_FOR_ACCESSIBILITY, announcement)
530                             .sendToTarget();
531                 }
532             }
533         };
534 
535         tileRecord.tile.addCallback(callback);
536         tileRecord.callback = callback;
537         tileRecord.tileView.init(tileRecord.tile);
538         tileRecord.tile.refreshState();
539 
540         if (mTileLayout != null) {
541             mTileLayout.addTile(tileRecord);
542         }
543     }
544 
removeTile(QSPanelControllerBase.TileRecord tileRecord)545     void removeTile(QSPanelControllerBase.TileRecord tileRecord) {
546         mTileLayout.removeTile(tileRecord);
547     }
548 
closeDetail()549     void closeDetail() {
550         showDetail(false, mDetailRecord);
551     }
552 
getGridHeight()553     public int getGridHeight() {
554         return getMeasuredHeight();
555     }
556 
handleShowDetail(Record r, boolean show)557     protected void handleShowDetail(Record r, boolean show) {
558         if (r instanceof QSPanelControllerBase.TileRecord) {
559             handleShowDetailTile((QSPanelControllerBase.TileRecord) r, show);
560         } else {
561             int x = 0;
562             int y = 0;
563             if (r != null) {
564                 x = r.x;
565                 y = r.y;
566             }
567             handleShowDetailImpl(r, show, x, y);
568         }
569     }
570 
handleShowDetailTile(QSPanelControllerBase.TileRecord r, boolean show)571     private void handleShowDetailTile(QSPanelControllerBase.TileRecord r, boolean show) {
572         if ((mDetailRecord != null) == show && mDetailRecord == r) return;
573 
574         if (show) {
575             r.detailAdapter = r.tile.getDetailAdapter();
576             if (r.detailAdapter == null) return;
577         }
578         r.tile.setDetailListening(show);
579         int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
580         int y = r.tileView.getDetailY() + mTileLayout.getOffsetTop(r) + getTop();
581         handleShowDetailImpl(r, show, x, y);
582     }
583 
handleShowDetailImpl(Record r, boolean show, int x, int y)584     private void handleShowDetailImpl(Record r, boolean show, int x, int y) {
585         setDetailRecord(show ? r : null);
586         fireShowingDetail(show ? r.detailAdapter : null, x, y);
587     }
588 
setDetailRecord(Record r)589     protected void setDetailRecord(Record r) {
590         if (r == mDetailRecord) return;
591         mDetailRecord = r;
592         final boolean scanState = mDetailRecord instanceof QSPanelControllerBase.TileRecord
593                 && ((QSPanelControllerBase.TileRecord) mDetailRecord).scanState;
594         fireScanStateChanged(scanState);
595     }
596 
fireShowingDetail(DetailAdapter detail, int x, int y)597     private void fireShowingDetail(DetailAdapter detail, int x, int y) {
598         if (mCallback != null) {
599             mCallback.onShowingDetail(detail, x, y);
600         }
601     }
602 
fireToggleStateChanged(boolean state)603     private void fireToggleStateChanged(boolean state) {
604         if (mCallback != null) {
605             mCallback.onToggleStateChanged(state);
606         }
607     }
608 
fireScanStateChanged(boolean state)609     private void fireScanStateChanged(boolean state) {
610         if (mCallback != null) {
611             mCallback.onScanStateChanged(state);
612         }
613     }
614 
getTileLayout()615     QSTileLayout getTileLayout() {
616         return mTileLayout;
617     }
618 
619     /** */
setContentMargins(int startMargin, int endMargin, ViewGroup mediaHostView)620     public void setContentMargins(int startMargin, int endMargin, ViewGroup mediaHostView) {
621         // Only some views actually want this content padding, others want to go all the way
622         // to the edge like the brightness slider
623         mContentMarginStart = startMargin;
624         mContentMarginEnd = endMargin;
625         updateMediaHostContentMargins(mediaHostView);
626     }
627 
628     /**
629      * Update the margins of the media hosts
630      */
updateMediaHostContentMargins(ViewGroup mediaHostView)631     protected void updateMediaHostContentMargins(ViewGroup mediaHostView) {
632         if (mUsingMediaPlayer) {
633             int marginStart = 0;
634             int marginEnd = 0;
635             if (mUsingHorizontalLayout) {
636                 marginEnd = mContentMarginEnd;
637             }
638             updateMargins(mediaHostView, marginStart, marginEnd);
639         }
640     }
641 
642     /**
643      * Update the margins of a view.
644      *
645      * @param view the view to adjust
646      * @param start the start margin to set
647      * @param end the end margin to set
648      */
updateMargins(View view, int start, int end)649     protected void updateMargins(View view, int start, int end) {
650         LayoutParams lp = (LayoutParams) view.getLayoutParams();
651         if (lp != null) {
652             lp.setMarginStart(start);
653             lp.setMarginEnd(end);
654             view.setLayoutParams(lp);
655         }
656     }
657 
658     /**
659      * Set the header container of quick settings.
660      */
setHeaderContainer(@onNull ViewGroup headerContainer)661     public void setHeaderContainer(@NonNull ViewGroup headerContainer) {
662         mHeaderContainer = headerContainer;
663     }
664 
isListening()665     public boolean isListening() {
666         return mListening;
667     }
668 
setSecurityFooter(View view)669     public void setSecurityFooter(View view) {
670         mSecurityFooter = view;
671         switchSecurityFooter();
672     }
673 
setPageMargin(int pageMargin)674     protected void setPageMargin(int pageMargin) {
675         if (mTileLayout instanceof PagedTileLayout) {
676             ((PagedTileLayout) mTileLayout).setPageMargin(pageMargin);
677         }
678     }
679 
setUsingHorizontalLayout(boolean horizontal, ViewGroup mediaHostView, boolean force)680     void setUsingHorizontalLayout(boolean horizontal, ViewGroup mediaHostView, boolean force) {
681         if (horizontal != mUsingHorizontalLayout || force) {
682             mUsingHorizontalLayout = horizontal;
683             ViewGroup newParent = horizontal ? mHorizontalContentContainer : this;
684             switchAllContentToParent(newParent, mTileLayout);
685             reAttachMediaHost(mediaHostView, horizontal);
686             if (needsDynamicRowsAndColumns()) {
687                 mTileLayout.setMinRows(horizontal ? 2 : 1);
688                 mTileLayout.setMaxColumns(horizontal ? 2 : 4);
689             }
690             updateMargins(mediaHostView);
691             mHorizontalLinearLayout.setVisibility(horizontal ? View.VISIBLE : View.GONE);
692         }
693     }
694 
updateMargins(ViewGroup mediaHostView)695     private void updateMargins(ViewGroup mediaHostView) {
696         updateMediaHostContentMargins(mediaHostView);
697         updateHorizontalLinearLayoutMargins();
698         updatePadding();
699     }
700 
701     private class H extends Handler {
702         private static final int SHOW_DETAIL = 1;
703         private static final int SET_TILE_VISIBILITY = 2;
704         private static final int ANNOUNCE_FOR_ACCESSIBILITY = 3;
705 
706         @Override
handleMessage(Message msg)707         public void handleMessage(Message msg) {
708             if (msg.what == SHOW_DETAIL) {
709                 handleShowDetail((Record) msg.obj, msg.arg1 != 0);
710             } else if (msg.what == ANNOUNCE_FOR_ACCESSIBILITY) {
711                 announceForAccessibility((CharSequence) msg.obj);
712             }
713         }
714     }
715 
716     protected static class Record {
717         DetailAdapter detailAdapter;
718         int x;
719         int y;
720     }
721 
722     public interface QSTileLayout {
723         /** */
saveInstanceState(Bundle outState)724         default void saveInstanceState(Bundle outState) {}
725 
726         /** */
restoreInstanceState(Bundle savedInstanceState)727         default void restoreInstanceState(Bundle savedInstanceState) {}
728 
729         /** */
addTile(QSPanelControllerBase.TileRecord tile)730         void addTile(QSPanelControllerBase.TileRecord tile);
731 
732         /** */
removeTile(QSPanelControllerBase.TileRecord tile)733         void removeTile(QSPanelControllerBase.TileRecord tile);
734 
735         /** */
getOffsetTop(QSPanelControllerBase.TileRecord tile)736         int getOffsetTop(QSPanelControllerBase.TileRecord tile);
737 
738         /** */
updateResources()739         boolean updateResources();
740 
741         /** */
setListening(boolean listening, UiEventLogger uiEventLogger)742         void setListening(boolean listening, UiEventLogger uiEventLogger);
743 
744         /**
745          * Sets the minimum number of rows to show
746          *
747          * @param minRows the minimum.
748          */
setMinRows(int minRows)749         default boolean setMinRows(int minRows) {
750             return false;
751         }
752 
753         /**
754          * Sets the max number of columns to show
755          *
756          * @param maxColumns the maximum
757          *
758          * @return true if the number of visible columns has changed.
759          */
setMaxColumns(int maxColumns)760         default boolean setMaxColumns(int maxColumns) {
761             return false;
762         }
763 
764         /**
765          * Sets the expansion value and proposedTranslation to panel.
766          */
setExpansion(float expansion, float proposedTranslation)767         default void setExpansion(float expansion, float proposedTranslation) {}
768 
getNumVisibleTiles()769         int getNumVisibleTiles();
770     }
771 
772     interface OnConfigurationChangedListener {
onConfigurationChange(Configuration newConfig)773         void onConfigurationChange(Configuration newConfig);
774     }
775 
776     @VisibleForTesting
switchToParent(View child, ViewGroup parent, int index, String tag)777     static void switchToParent(View child, ViewGroup parent, int index, String tag) {
778         if (parent == null) {
779             Log.w(tag, "Trying to move view to null parent",
780                     new IllegalStateException());
781             return;
782         }
783         ViewGroup currentParent = (ViewGroup) child.getParent();
784         if (currentParent != parent) {
785             if (currentParent != null) {
786                 currentParent.removeView(child);
787             }
788             parent.addView(child, index);
789             return;
790         }
791         // Same parent, we are just changing indices
792         int currentIndex = parent.indexOfChild(child);
793         if (currentIndex == index) {
794             // We want to be in the same place. Nothing to do here
795             return;
796         }
797         parent.removeView(child);
798         parent.addView(child, index);
799     }
800 }
801