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