QSPanel.java revision 5fd872e8ea44ffcd4fce1d1196736dca4f270508
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.animation.Animator; 20import android.animation.Animator.AnimatorListener; 21import android.animation.AnimatorListenerAdapter; 22import android.content.Context; 23import android.content.Intent; 24import android.content.res.Resources; 25import android.os.Handler; 26import android.os.Message; 27import android.util.AttributeSet; 28import android.view.LayoutInflater; 29import android.view.View; 30import android.view.ViewGroup; 31import android.widget.ImageView; 32 33import com.android.systemui.R; 34import com.android.systemui.qs.QSTile.DetailAdapter; 35import com.android.systemui.settings.BrightnessController; 36import com.android.systemui.settings.ToggleSlider; 37import com.android.systemui.statusbar.phone.QSTileHost; 38import com.android.systemui.statusbar.policy.BrightnessMirrorController; 39 40import java.util.ArrayList; 41import java.util.Collection; 42 43/** View that represents the quick settings tile panel. **/ 44public class QSPanel extends ViewGroup { 45 private static final float TILE_ASPECT = 1.2f; 46 47 private final Context mContext; 48 private final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>(); 49 private final View mDetail; 50 private final ViewGroup mDetailContent; 51 private final View mDetailSettingsButton; 52 private final View mDetailDoneButton; 53 private final View mBrightnessView; 54 private final QSDetailClipper mClipper; 55 private final H mHandler = new H(); 56 57 private int mColumns; 58 private int mCellWidth; 59 private int mCellHeight; 60 private int mLargeCellWidth; 61 private int mLargeCellHeight; 62 private int mPanelPaddingBottom; 63 private int mDualTileUnderlap; 64 private boolean mExpanded; 65 private boolean mListening; 66 67 private Record mDetailRecord; 68 private Callback mCallback; 69 private BrightnessController mBrightnessController; 70 private QSTileHost mHost; 71 72 private QSFooter mFooter; 73 74 public QSPanel(Context context) { 75 this(context, null); 76 } 77 78 public QSPanel(Context context, AttributeSet attrs) { 79 super(context, attrs); 80 mContext = context; 81 82 mDetail = LayoutInflater.from(context).inflate(R.layout.qs_detail, this, false); 83 mDetailContent = (ViewGroup) mDetail.findViewById(android.R.id.content); 84 mDetailSettingsButton = mDetail.findViewById(android.R.id.button2); 85 mDetailDoneButton = mDetail.findViewById(android.R.id.button1); 86 mDetail.setVisibility(GONE); 87 mDetail.setClickable(true); 88 mBrightnessView = LayoutInflater.from(context).inflate( 89 R.layout.quick_settings_brightness_dialog, this, false); 90 mFooter = new QSFooter(this, context); 91 addView(mDetail); 92 addView(mBrightnessView); 93 addView(mFooter.getView()); 94 mClipper = new QSDetailClipper(mDetail); 95 updateResources(); 96 97 mBrightnessController = new BrightnessController(getContext(), 98 (ImageView) findViewById(R.id.brightness_icon), 99 (ToggleSlider) findViewById(R.id.brightness_slider)); 100 101 mDetailDoneButton.setOnClickListener(new OnClickListener() { 102 @Override 103 public void onClick(View v) { 104 closeDetail(); 105 } 106 }); 107 } 108 109 public void setBrightnessMirror(BrightnessMirrorController c) { 110 super.onFinishInflate(); 111 ToggleSlider brightnessSlider = (ToggleSlider) findViewById(R.id.brightness_slider); 112 ToggleSlider mirror = (ToggleSlider) c.getMirror().findViewById(R.id.brightness_slider); 113 brightnessSlider.setMirror(mirror); 114 brightnessSlider.setMirrorController(c); 115 } 116 117 public void setCallback(Callback callback) { 118 mCallback = callback; 119 } 120 121 public void setHost(QSTileHost host) { 122 mHost = host; 123 mFooter.setHost(host); 124 } 125 126 public QSTileHost getHost() { 127 return mHost; 128 } 129 130 public void updateResources() { 131 final Resources res = mContext.getResources(); 132 final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns)); 133 mCellHeight = res.getDimensionPixelSize(R.dimen.qs_tile_height); 134 mCellWidth = (int)(mCellHeight * TILE_ASPECT); 135 mLargeCellHeight = res.getDimensionPixelSize(R.dimen.qs_dual_tile_height); 136 mLargeCellWidth = (int)(mLargeCellHeight * TILE_ASPECT); 137 mPanelPaddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom); 138 mDualTileUnderlap = res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_vertical); 139 if (mColumns != columns) { 140 mColumns = columns; 141 postInvalidate(); 142 } 143 if (mListening) { 144 refreshAllTiles(); 145 } 146 } 147 148 public void setExpanded(boolean expanded) { 149 if (mExpanded == expanded) return; 150 mExpanded = expanded; 151 if (!mExpanded) { 152 closeDetail(); 153 } 154 } 155 156 public void setListening(boolean listening) { 157 if (mListening == listening) return; 158 mListening = listening; 159 for (TileRecord r : mRecords) { 160 r.tile.setListening(mListening); 161 } 162 mFooter.setListening(mListening); 163 if (mListening) { 164 refreshAllTiles(); 165 } 166 if (listening) { 167 mBrightnessController.registerCallbacks(); 168 } else { 169 mBrightnessController.unregisterCallbacks(); 170 } 171 } 172 173 private void refreshAllTiles() { 174 for (TileRecord r : mRecords) { 175 r.tile.refreshState(); 176 } 177 mFooter.refreshState(); 178 } 179 180 public void showDetailAdapter(boolean show, DetailAdapter adapter) { 181 Record r = new Record(); 182 r.detailAdapter = adapter; 183 showDetail(show, r); 184 } 185 186 private void showDetail(boolean show, Record r) { 187 mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget(); 188 } 189 190 private void setTileVisibility(View v, boolean visible) { 191 mHandler.obtainMessage(H.SET_TILE_VISIBILITY, visible ? 1 : 0, 0, v).sendToTarget(); 192 } 193 194 private void handleSetTileVisibility(View v, boolean visible) { 195 if (visible == (v.getVisibility() == VISIBLE)) return; 196 v.setVisibility(visible ? VISIBLE : GONE); 197 } 198 199 public void setTiles(Collection<QSTile<?>> tiles) { 200 for (TileRecord record : mRecords) { 201 removeView(record.tileView); 202 } 203 mRecords.clear(); 204 for (QSTile<?> tile : tiles) { 205 addTile(tile); 206 } 207 if (isShowingDetail()) { 208 mDetail.bringToFront(); 209 } 210 } 211 212 private void addTile(final QSTile<?> tile) { 213 final TileRecord r = new TileRecord(); 214 r.tile = tile; 215 r.tileView = tile.createTileView(mContext); 216 r.tileView.setVisibility(View.GONE); 217 final QSTile.Callback callback = new QSTile.Callback() { 218 @Override 219 public void onStateChanged(QSTile.State state) { 220 setTileVisibility(r.tileView, state.visible); 221 r.tileView.onStateChanged(state); 222 } 223 @Override 224 public void onShowDetail(boolean show) { 225 QSPanel.this.showDetail(show, r); 226 } 227 @Override 228 public void onToggleStateChanged(boolean state) { 229 if (mDetailRecord == r) { 230 fireToggleStateChanged(state); 231 } 232 } 233 @Override 234 public void onScanStateChanged(boolean state) { 235 if (mDetailRecord == r) { 236 fireScanStateChanged(state); 237 } 238 } 239 }; 240 r.tile.setCallback(callback); 241 final View.OnClickListener click = new View.OnClickListener() { 242 @Override 243 public void onClick(View v) { 244 r.tile.click(); 245 } 246 }; 247 final View.OnClickListener clickSecondary = new View.OnClickListener() { 248 @Override 249 public void onClick(View v) { 250 r.tile.secondaryClick(); 251 } 252 }; 253 r.tileView.init(click, clickSecondary); 254 r.tile.setListening(mListening); 255 callback.onStateChanged(r.tile.getState()); 256 r.tile.refreshState(); 257 mRecords.add(r); 258 259 addView(r.tileView); 260 } 261 262 public boolean isShowingDetail() { 263 return mDetailRecord != null; 264 } 265 266 public void closeDetail() { 267 showDetail(false, mDetailRecord); 268 } 269 270 private void handleShowDetail(Record r, boolean show) { 271 if (r instanceof TileRecord) { 272 handleShowDetailTile((TileRecord) r, show); 273 } else { 274 handleShowDetailImpl(r, show, getWidth() /* x */, 0/* y */); 275 } 276 } 277 278 private void handleShowDetailTile(TileRecord r, boolean show) { 279 if ((mDetailRecord != null) == show) return; 280 281 if (show) { 282 r.detailAdapter = r.tile.getDetailAdapter(); 283 if (r.detailAdapter == null) return; 284 } 285 int x = r.tileView.getLeft() + r.tileView.getWidth() / 2; 286 int y = r.tileView.getTop() + r.tileView.getHeight() / 2; 287 handleShowDetailImpl(r, show, x, y); 288 } 289 290 private void handleShowDetailImpl(Record r, boolean show, int x, int y) { 291 if ((mDetailRecord != null) == show) return; // already in right state 292 DetailAdapter detailAdapter = null; 293 AnimatorListener listener = null; 294 if (show) { 295 detailAdapter = r.detailAdapter; 296 r.detailView = detailAdapter.createDetailView(mContext, r.detailView, mDetailContent); 297 if (r.detailView == null) throw new IllegalStateException("Must return detail view"); 298 299 final Intent settingsIntent = detailAdapter.getSettingsIntent(); 300 mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE); 301 mDetailSettingsButton.setOnClickListener(new OnClickListener() { 302 @Override 303 public void onClick(View v) { 304 mHost.startSettingsActivity(settingsIntent); 305 } 306 }); 307 308 mDetailContent.removeAllViews(); 309 mDetail.bringToFront(); 310 mDetailContent.addView(r.detailView); 311 mDetailRecord = r; 312 } else { 313 listener = mTeardownDetailWhenDone; 314 } 315 fireShowingDetail(show ? detailAdapter : null); 316 mClipper.animateCircularClip(x, y, show, listener); 317 } 318 319 @Override 320 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 321 final int width = MeasureSpec.getSize(widthMeasureSpec); 322 mBrightnessView.measure(exactly(width), MeasureSpec.UNSPECIFIED); 323 mFooter.getView().measure(exactly(width), MeasureSpec.UNSPECIFIED); 324 int r = -1; 325 int c = -1; 326 int rows = 0; 327 boolean rowIsDual = false; 328 for (TileRecord record : mRecords) { 329 if (record.tileView.getVisibility() == GONE) continue; 330 // wrap to next column if we've reached the max # of columns 331 // also don't allow dual + single tiles on the same row 332 if (r == -1 || c == (mColumns - 1) || rowIsDual != record.tile.supportsDualTargets()) { 333 r++; 334 c = 0; 335 rowIsDual = record.tile.supportsDualTargets(); 336 } else { 337 c++; 338 } 339 record.row = r; 340 record.col = c; 341 rows = r + 1; 342 } 343 344 for (TileRecord record : mRecords) { 345 if (record.tileView.getVisibility() == GONE) continue; 346 record.tileView.setDual(record.tile.supportsDualTargets()); 347 final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth; 348 final int ch = record.row == 0 ? mLargeCellHeight : mCellHeight; 349 record.tileView.measure(exactly(cw), exactly(ch)); 350 } 351 int h = rows == 0 ? mBrightnessView.getHeight() : (getRowTop(rows) + mPanelPaddingBottom); 352 if (mFooter.hasFooter()) { 353 h += mFooter.getView().getHeight(); 354 } 355 mDetail.measure(exactly(width), MeasureSpec.UNSPECIFIED); 356 if (mDetail.getMeasuredHeight() < h) { 357 mDetail.measure(exactly(width), exactly(h)); 358 } 359 setMeasuredDimension(width, Math.max(h, mDetail.getMeasuredHeight())); 360 } 361 362 private static int exactly(int size) { 363 return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); 364 } 365 366 @Override 367 protected void onLayout(boolean changed, int l, int t, int r, int b) { 368 final int w = getWidth(); 369 mBrightnessView.layout(0, 0, 370 mBrightnessView.getMeasuredWidth(), mBrightnessView.getMeasuredHeight()); 371 for (TileRecord record : mRecords) { 372 if (record.tileView.getVisibility() == GONE) continue; 373 final int cols = getColumnCount(record.row); 374 final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth; 375 final int extra = (w - cw * cols) / (cols + 1); 376 final int left = record.col * cw + (record.col + 1) * extra; 377 final int top = getRowTop(record.row); 378 record.tileView.layout(left, top, 379 left + record.tileView.getMeasuredWidth(), 380 top + record.tileView.getMeasuredHeight()); 381 } 382 final int dh = Math.max(mDetail.getMeasuredHeight(), getMeasuredHeight()); 383 mDetail.layout(0, 0, mDetail.getMeasuredWidth(), dh); 384 if (mFooter.hasFooter()) { 385 View footer = mFooter.getView(); 386 footer.layout(0, getMeasuredHeight() - footer.getMeasuredHeight(), 387 footer.getMeasuredWidth(), getMeasuredHeight()); 388 } 389 } 390 391 private int getRowTop(int row) { 392 if (row <= 0) return mBrightnessView.getHeight(); 393 return mBrightnessView.getHeight() 394 + mLargeCellHeight - mDualTileUnderlap + (row - 1) * mCellHeight; 395 } 396 397 private int getColumnCount(int row) { 398 int cols = 0; 399 for (TileRecord record : mRecords) { 400 if (record.tileView.getVisibility() == GONE) continue; 401 if (record.row == row) cols++; 402 } 403 return cols; 404 } 405 406 private void fireShowingDetail(QSTile.DetailAdapter detail) { 407 if (mCallback != null) { 408 mCallback.onShowingDetail(detail); 409 } 410 } 411 412 private void fireToggleStateChanged(boolean state) { 413 if (mCallback != null) { 414 mCallback.onToggleStateChanged(state); 415 } 416 } 417 418 private void fireScanStateChanged(boolean state) { 419 if (mCallback != null) { 420 mCallback.onScanStateChanged(state); 421 } 422 } 423 424 private class H extends Handler { 425 private static final int SHOW_DETAIL = 1; 426 private static final int SET_TILE_VISIBILITY = 2; 427 @Override 428 public void handleMessage(Message msg) { 429 if (msg.what == SHOW_DETAIL) { 430 handleShowDetail((Record)msg.obj, msg.arg1 != 0); 431 } else if (msg.what == SET_TILE_VISIBILITY) { 432 handleSetTileVisibility((View)msg.obj, msg.arg1 != 0); 433 } 434 } 435 } 436 437 private static class Record { 438 View detailView; 439 DetailAdapter detailAdapter; 440 } 441 442 private static final class TileRecord extends Record { 443 QSTile<?> tile; 444 QSTileView tileView; 445 int row; 446 int col; 447 } 448 449 private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() { 450 public void onAnimationEnd(Animator animation) { 451 mDetailContent.removeAllViews(); 452 mDetailRecord = null; 453 }; 454 }; 455 456 public interface Callback { 457 void onShowingDetail(QSTile.DetailAdapter detail); 458 void onToggleStateChanged(boolean state); 459 void onScanStateChanged(boolean state); 460 } 461} 462