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