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