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