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 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.res.Configuration; 24 import android.content.res.Resources; 25 import android.metrics.LogMaker; 26 import android.os.Handler; 27 import android.os.Message; 28 import android.service.quicksettings.Tile; 29 import android.util.AttributeSet; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.widget.ImageView; 33 import android.widget.LinearLayout; 34 35 import com.android.internal.logging.MetricsLogger; 36 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 37 import com.android.settingslib.Utils; 38 import com.android.systemui.Dependency; 39 import com.android.systemui.R; 40 import com.android.systemui.plugins.qs.DetailAdapter; 41 import com.android.systemui.plugins.qs.QSTile; 42 import com.android.systemui.plugins.qs.QSTileView; 43 import com.android.systemui.qs.QSHost.Callback; 44 import com.android.systemui.qs.customize.QSCustomizer; 45 import com.android.systemui.qs.external.CustomTile; 46 import com.android.systemui.settings.BrightnessController; 47 import com.android.systemui.settings.ToggleSliderView; 48 import com.android.systemui.statusbar.policy.BrightnessMirrorController; 49 import com.android.systemui.tuner.TunerService; 50 import com.android.systemui.tuner.TunerService.Tunable; 51 52 import java.util.ArrayList; 53 import java.util.Collection; 54 55 /** View that represents the quick settings tile panel. **/ 56 public class QSPanel extends LinearLayout implements Tunable, Callback { 57 58 public static final String QS_SHOW_BRIGHTNESS = "qs_show_brightness"; 59 60 protected final Context mContext; 61 protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>(); 62 protected final View mBrightnessView; 63 private final H mHandler = new H(); 64 private final View mPageIndicator; 65 private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); 66 67 private int mPanelPaddingBottom; 68 private int mBrightnessPaddingTop; 69 protected boolean mExpanded; 70 protected boolean mListening; 71 72 private QSDetail.Callback mCallback; 73 private BrightnessController mBrightnessController; 74 protected QSTileHost mHost; 75 76 protected QSSecurityFooter mFooter; 77 private boolean mGridContentVisible = true; 78 79 protected QSTileLayout mTileLayout; 80 81 private QSCustomizer mCustomizePanel; 82 private Record mDetailRecord; 83 84 private BrightnessMirrorController mBrightnessMirrorController; 85 private View mDivider; 86 QSPanel(Context context)87 public QSPanel(Context context) { 88 this(context, null); 89 } 90 QSPanel(Context context, AttributeSet attrs)91 public QSPanel(Context context, AttributeSet attrs) { 92 super(context, attrs); 93 mContext = context; 94 95 setOrientation(VERTICAL); 96 97 mBrightnessView = LayoutInflater.from(context).inflate( 98 R.layout.quick_settings_brightness_dialog, this, false); 99 addView(mBrightnessView); 100 101 setupTileLayout(); 102 103 mPageIndicator = LayoutInflater.from(context).inflate( 104 R.layout.qs_page_indicator, this, false); 105 addView(mPageIndicator); 106 if (mTileLayout instanceof PagedTileLayout) { 107 ((PagedTileLayout) mTileLayout).setPageIndicator((PageIndicator) mPageIndicator); 108 } 109 110 addDivider(); 111 112 mFooter = new QSSecurityFooter(this, context); 113 addView(mFooter.getView()); 114 115 updateResources(); 116 117 mBrightnessController = new BrightnessController(getContext(), 118 findViewById(R.id.brightness_icon), 119 findViewById(R.id.brightness_slider)); 120 } 121 addDivider()122 protected void addDivider() { 123 mDivider = LayoutInflater.from(mContext).inflate(R.layout.qs_divider, this, false); 124 mDivider.setBackgroundColor(Utils.applyAlpha(mDivider.getAlpha(), 125 getColorForState(mContext, Tile.STATE_ACTIVE))); 126 addView(mDivider); 127 } 128 getDivider()129 public View getDivider() { 130 return mDivider; 131 } 132 getPageIndicator()133 public View getPageIndicator() { 134 return mPageIndicator; 135 } 136 setupTileLayout()137 protected void setupTileLayout() { 138 mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate( 139 R.layout.qs_paged_tile_layout, this, false); 140 mTileLayout.setListening(mListening); 141 addView((View) mTileLayout); 142 } 143 isShowingCustomize()144 public boolean isShowingCustomize() { 145 return mCustomizePanel != null && mCustomizePanel.isCustomizing(); 146 } 147 148 @Override onAttachedToWindow()149 protected void onAttachedToWindow() { 150 super.onAttachedToWindow(); 151 Dependency.get(TunerService.class).addTunable(this, QS_SHOW_BRIGHTNESS); 152 if (mHost != null) { 153 setTiles(mHost.getTiles()); 154 } 155 } 156 157 @Override onDetachedFromWindow()158 protected void onDetachedFromWindow() { 159 Dependency.get(TunerService.class).removeTunable(this); 160 if (mHost != null) { 161 mHost.removeCallback(this); 162 } 163 for (TileRecord record : mRecords) { 164 record.tile.removeCallbacks(); 165 } 166 super.onDetachedFromWindow(); 167 } 168 169 @Override onTilesChanged()170 public void onTilesChanged() { 171 setTiles(mHost.getTiles()); 172 } 173 174 @Override onTuningChanged(String key, String newValue)175 public void onTuningChanged(String key, String newValue) { 176 if (QS_SHOW_BRIGHTNESS.equals(key)) { 177 mBrightnessView.setVisibility(newValue == null || Integer.parseInt(newValue) != 0 178 ? VISIBLE : GONE); 179 } 180 } 181 openDetails(String subPanel)182 public void openDetails(String subPanel) { 183 QSTile tile = getTile(subPanel); 184 showDetailAdapter(true, tile.getDetailAdapter(), new int[]{getWidth() / 2, 0}); 185 } 186 getTile(String subPanel)187 private QSTile getTile(String subPanel) { 188 for (int i = 0; i < mRecords.size(); i++) { 189 if (subPanel.equals(mRecords.get(i).tile.getTileSpec())) { 190 return mRecords.get(i).tile; 191 } 192 } 193 return mHost.createTile(subPanel); 194 } 195 setBrightnessMirror(BrightnessMirrorController c)196 public void setBrightnessMirror(BrightnessMirrorController c) { 197 mBrightnessMirrorController = c; 198 ToggleSliderView brightnessSlider = findViewById(R.id.brightness_slider); 199 ToggleSliderView mirror = c.getMirror().findViewById( 200 R.id.brightness_slider); 201 brightnessSlider.setMirror(mirror); 202 brightnessSlider.setMirrorController(c); 203 } 204 getBrightnessView()205 View getBrightnessView() { 206 return mBrightnessView; 207 } 208 setCallback(QSDetail.Callback callback)209 public void setCallback(QSDetail.Callback callback) { 210 mCallback = callback; 211 } 212 setHost(QSTileHost host, QSCustomizer customizer)213 public void setHost(QSTileHost host, QSCustomizer customizer) { 214 mHost = host; 215 mHost.addCallback(this); 216 setTiles(mHost.getTiles()); 217 mFooter.setHostEnvironment(host); 218 mCustomizePanel = customizer; 219 if (mCustomizePanel != null) { 220 mCustomizePanel.setHost(mHost); 221 } 222 } 223 getHost()224 public QSTileHost getHost() { 225 return mHost; 226 } 227 updateResources()228 public void updateResources() { 229 final Resources res = mContext.getResources(); 230 mPanelPaddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom); 231 mBrightnessPaddingTop = res.getDimensionPixelSize(R.dimen.qs_brightness_padding_top); 232 setPadding(0, mBrightnessPaddingTop, 0, mPanelPaddingBottom); 233 for (TileRecord r : mRecords) { 234 r.tile.clearState(); 235 } 236 if (mListening) { 237 refreshAllTiles(); 238 } 239 if (mTileLayout != null) { 240 mTileLayout.updateResources(); 241 } 242 } 243 244 @Override onConfigurationChanged(Configuration newConfig)245 protected void onConfigurationChanged(Configuration newConfig) { 246 super.onConfigurationChanged(newConfig); 247 mFooter.onConfigurationChanged(); 248 249 if (mBrightnessMirrorController != null) { 250 // Reload the mirror in case it got reinflated but we didn't. 251 setBrightnessMirror(mBrightnessMirrorController); 252 } 253 } 254 onCollapse()255 public void onCollapse() { 256 if (mCustomizePanel != null && mCustomizePanel.isShown()) { 257 mCustomizePanel.hide(mCustomizePanel.getWidth() / 2, mCustomizePanel.getHeight() / 2); 258 } 259 } 260 setExpanded(boolean expanded)261 public void setExpanded(boolean expanded) { 262 if (mExpanded == expanded) return; 263 mExpanded = expanded; 264 if (!mExpanded && mTileLayout instanceof PagedTileLayout) { 265 ((PagedTileLayout) mTileLayout).setCurrentItem(0, false); 266 } 267 mMetricsLogger.visibility(MetricsEvent.QS_PANEL, mExpanded); 268 if (!mExpanded) { 269 closeDetail(); 270 } else { 271 logTiles(); 272 } 273 } 274 isExpanded()275 public boolean isExpanded() { 276 return mExpanded; 277 } 278 setListening(boolean listening)279 public void setListening(boolean listening) { 280 if (mListening == listening) return; 281 mListening = listening; 282 if (mTileLayout != null) { 283 mTileLayout.setListening(listening); 284 } 285 mFooter.setListening(mListening); 286 if (mListening) { 287 refreshAllTiles(); 288 } 289 if (mBrightnessView.getVisibility() == View.VISIBLE) { 290 if (listening) { 291 mBrightnessController.registerCallbacks(); 292 } else { 293 mBrightnessController.unregisterCallbacks(); 294 } 295 } 296 } 297 refreshAllTiles()298 public void refreshAllTiles() { 299 for (TileRecord r : mRecords) { 300 r.tile.refreshState(); 301 } 302 mFooter.refreshState(); 303 } 304 showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow)305 public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) { 306 int xInWindow = locationInWindow[0]; 307 int yInWindow = locationInWindow[1]; 308 ((View) getParent()).getLocationInWindow(locationInWindow); 309 310 Record r = new Record(); 311 r.detailAdapter = adapter; 312 r.x = xInWindow - locationInWindow[0]; 313 r.y = yInWindow - locationInWindow[1]; 314 315 locationInWindow[0] = xInWindow; 316 locationInWindow[1] = yInWindow; 317 318 showDetail(show, r); 319 } 320 showDetail(boolean show, Record r)321 protected void showDetail(boolean show, Record r) { 322 mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget(); 323 } 324 setTiles(Collection<QSTile> tiles)325 public void setTiles(Collection<QSTile> tiles) { 326 setTiles(tiles, false); 327 } 328 setTiles(Collection<QSTile> tiles, boolean collapsedView)329 public void setTiles(Collection<QSTile> tiles, boolean collapsedView) { 330 for (TileRecord record : mRecords) { 331 mTileLayout.removeTile(record); 332 record.tile.removeCallback(record.callback); 333 } 334 mRecords.clear(); 335 for (QSTile tile : tiles) { 336 addTile(tile, collapsedView); 337 } 338 } 339 drawTile(TileRecord r, QSTile.State state)340 protected void drawTile(TileRecord r, QSTile.State state) { 341 r.tileView.onStateChanged(state); 342 } 343 createTileView(QSTile tile, boolean collapsedView)344 protected QSTileView createTileView(QSTile tile, boolean collapsedView) { 345 return mHost.createTileView(tile, collapsedView); 346 } 347 shouldShowDetail()348 protected boolean shouldShowDetail() { 349 return mExpanded; 350 } 351 addTile(final QSTile tile, boolean collapsedView)352 protected TileRecord addTile(final QSTile tile, boolean collapsedView) { 353 final TileRecord r = new TileRecord(); 354 r.tile = tile; 355 r.tileView = createTileView(tile, collapsedView); 356 final QSTile.Callback callback = new QSTile.Callback() { 357 @Override 358 public void onStateChanged(QSTile.State state) { 359 drawTile(r, state); 360 } 361 362 @Override 363 public void onShowDetail(boolean show) { 364 // Both the collapsed and full QS panels get this callback, this check determines 365 // which one should handle showing the detail. 366 if (shouldShowDetail()) { 367 QSPanel.this.showDetail(show, r); 368 } 369 } 370 371 @Override 372 public void onToggleStateChanged(boolean state) { 373 if (mDetailRecord == r) { 374 fireToggleStateChanged(state); 375 } 376 } 377 378 @Override 379 public void onScanStateChanged(boolean state) { 380 r.scanState = state; 381 if (mDetailRecord == r) { 382 fireScanStateChanged(r.scanState); 383 } 384 } 385 386 @Override 387 public void onAnnouncementRequested(CharSequence announcement) { 388 if (announcement != null) { 389 mHandler.obtainMessage(H.ANNOUNCE_FOR_ACCESSIBILITY, announcement) 390 .sendToTarget(); 391 } 392 } 393 }; 394 r.tile.addCallback(callback); 395 r.callback = callback; 396 r.tileView.init(r.tile); 397 r.tile.refreshState(); 398 mRecords.add(r); 399 400 if (mTileLayout != null) { 401 mTileLayout.addTile(r); 402 } 403 404 return r; 405 } 406 407 showEdit(final View v)408 public void showEdit(final View v) { 409 v.post(new Runnable() { 410 @Override 411 public void run() { 412 if (mCustomizePanel != null) { 413 if (!mCustomizePanel.isCustomizing()) { 414 int[] loc = new int[2]; 415 v.getLocationInWindow(loc); 416 int x = loc[0] + v.getWidth() / 2; 417 int y = loc[1] + v.getHeight() / 2; 418 mCustomizePanel.show(x, y); 419 } 420 } 421 422 } 423 }); 424 } 425 closeDetail()426 public void closeDetail() { 427 if (mCustomizePanel != null && mCustomizePanel.isShown()) { 428 // Treat this as a detail panel for now, to make things easy. 429 mCustomizePanel.hide(mCustomizePanel.getWidth() / 2, mCustomizePanel.getHeight() / 2); 430 return; 431 } 432 showDetail(false, mDetailRecord); 433 } 434 getGridHeight()435 public int getGridHeight() { 436 return getMeasuredHeight(); 437 } 438 handleShowDetail(Record r, boolean show)439 protected void handleShowDetail(Record r, boolean show) { 440 if (r instanceof TileRecord) { 441 handleShowDetailTile((TileRecord) r, show); 442 } else { 443 int x = 0; 444 int y = 0; 445 if (r != null) { 446 x = r.x; 447 y = r.y; 448 } 449 handleShowDetailImpl(r, show, x, y); 450 } 451 } 452 handleShowDetailTile(TileRecord r, boolean show)453 private void handleShowDetailTile(TileRecord r, boolean show) { 454 if ((mDetailRecord != null) == show && mDetailRecord == r) return; 455 456 if (show) { 457 r.detailAdapter = r.tile.getDetailAdapter(); 458 if (r.detailAdapter == null) return; 459 } 460 r.tile.setDetailListening(show); 461 int x = r.tileView.getLeft() + r.tileView.getWidth() / 2; 462 int y = r.tileView.getDetailY() + mTileLayout.getOffsetTop(r) + getTop(); 463 handleShowDetailImpl(r, show, x, y); 464 } 465 handleShowDetailImpl(Record r, boolean show, int x, int y)466 private void handleShowDetailImpl(Record r, boolean show, int x, int y) { 467 setDetailRecord(show ? r : null); 468 fireShowingDetail(show ? r.detailAdapter : null, x, y); 469 } 470 setDetailRecord(Record r)471 protected void setDetailRecord(Record r) { 472 if (r == mDetailRecord) return; 473 mDetailRecord = r; 474 final boolean scanState = mDetailRecord instanceof TileRecord 475 && ((TileRecord) mDetailRecord).scanState; 476 fireScanStateChanged(scanState); 477 } 478 setGridContentVisibility(boolean visible)479 void setGridContentVisibility(boolean visible) { 480 int newVis = visible ? VISIBLE : INVISIBLE; 481 setVisibility(newVis); 482 if (mGridContentVisible != visible) { 483 mMetricsLogger.visibility(MetricsEvent.QS_PANEL, newVis); 484 } 485 mGridContentVisible = visible; 486 } 487 logTiles()488 private void logTiles() { 489 for (int i = 0; i < mRecords.size(); i++) { 490 QSTile tile = mRecords.get(i).tile; 491 mMetricsLogger.write(tile.populate(new LogMaker(tile.getMetricsCategory()) 492 .setType(MetricsEvent.TYPE_OPEN))); 493 } 494 } 495 fireShowingDetail(DetailAdapter detail, int x, int y)496 private void fireShowingDetail(DetailAdapter detail, int x, int y) { 497 if (mCallback != null) { 498 mCallback.onShowingDetail(detail, x, y); 499 } 500 } 501 fireToggleStateChanged(boolean state)502 private void fireToggleStateChanged(boolean state) { 503 if (mCallback != null) { 504 mCallback.onToggleStateChanged(state); 505 } 506 } 507 fireScanStateChanged(boolean state)508 private void fireScanStateChanged(boolean state) { 509 if (mCallback != null) { 510 mCallback.onScanStateChanged(state); 511 } 512 } 513 clickTile(ComponentName tile)514 public void clickTile(ComponentName tile) { 515 final String spec = CustomTile.toSpec(tile); 516 final int N = mRecords.size(); 517 for (int i = 0; i < N; i++) { 518 if (mRecords.get(i).tile.getTileSpec().equals(spec)) { 519 mRecords.get(i).tile.click(); 520 break; 521 } 522 } 523 } 524 getTileLayout()525 QSTileLayout getTileLayout() { 526 return mTileLayout; 527 } 528 getTileView(QSTile tile)529 QSTileView getTileView(QSTile tile) { 530 for (TileRecord r : mRecords) { 531 if (r.tile == tile) { 532 return r.tileView; 533 } 534 } 535 return null; 536 } 537 getFooter()538 public QSSecurityFooter getFooter() { 539 return mFooter; 540 } 541 showDeviceMonitoringDialog()542 public void showDeviceMonitoringDialog() { 543 mFooter.showDeviceMonitoringDialog(); 544 } 545 546 private class H extends Handler { 547 private static final int SHOW_DETAIL = 1; 548 private static final int SET_TILE_VISIBILITY = 2; 549 private static final int ANNOUNCE_FOR_ACCESSIBILITY = 3; 550 551 @Override handleMessage(Message msg)552 public void handleMessage(Message msg) { 553 if (msg.what == SHOW_DETAIL) { 554 handleShowDetail((Record) msg.obj, msg.arg1 != 0); 555 } else if (msg.what == ANNOUNCE_FOR_ACCESSIBILITY) { 556 announceForAccessibility((CharSequence) msg.obj); 557 } 558 } 559 } 560 561 protected static class Record { 562 DetailAdapter detailAdapter; 563 int x; 564 int y; 565 } 566 567 public static final class TileRecord extends Record { 568 public QSTile tile; 569 public com.android.systemui.plugins.qs.QSTileView tileView; 570 public boolean scanState; 571 public QSTile.Callback callback; 572 } 573 574 public interface QSTileLayout { addTile(TileRecord tile)575 void addTile(TileRecord tile); 576 removeTile(TileRecord tile)577 void removeTile(TileRecord tile); 578 getOffsetTop(TileRecord tile)579 int getOffsetTop(TileRecord tile); 580 updateResources()581 boolean updateResources(); 582 setListening(boolean listening)583 void setListening(boolean listening); 584 } 585 } 586