blob: 449cc1dfea3b2cbb26ff2c143ab8d70239837f6e [file] [log] [blame]
John Spurlockaf8d6c42014-05-07 17:49:08 -04001/*
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;
John Spurlock7f8f22a2014-07-02 18:54:17 -040023import android.content.Intent;
John Spurlock4bf31982014-05-21 13:04:22 -040024import android.content.res.Resources;
John Spurlockaf8d6c42014-05-07 17:49:08 -040025import android.os.Handler;
26import android.os.Message;
27import android.util.AttributeSet;
John Spurlock7f8f22a2014-07-02 18:54:17 -040028import android.view.LayoutInflater;
John Spurlockaf8d6c42014-05-07 17:49:08 -040029import android.view.View;
30import android.view.ViewGroup;
Jorim Jaggi3f48f462014-07-08 16:53:29 +020031import android.widget.ImageView;
John Spurlockaf8d6c42014-05-07 17:49:08 -040032
33import com.android.systemui.R;
John Spurlock7f8f22a2014-07-02 18:54:17 -040034import com.android.systemui.qs.QSTile.DetailAdapter;
Jorim Jaggi3f48f462014-07-08 16:53:29 +020035import com.android.systemui.settings.BrightnessController;
36import com.android.systemui.settings.ToggleSlider;
John Spurlockaf8d6c42014-05-07 17:49:08 -040037
38import java.util.ArrayList;
39
40/** View that represents the quick settings tile panel. **/
41public class QSPanel extends ViewGroup {
John Spurlock4bf31982014-05-21 13:04:22 -040042 private static final float TILE_ASPECT = 1.2f;
John Spurlockaf8d6c42014-05-07 17:49:08 -040043
44 private final Context mContext;
45 private final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
John Spurlock7f8f22a2014-07-02 18:54:17 -040046 private final View mDetail;
47 private final ViewGroup mDetailContent;
48 private final View mDetailSettingsButton;
49 private final View mDetailDoneButton;
Jorim Jaggi3f48f462014-07-08 16:53:29 +020050 private final View mBrightnessView;
John Spurlockaf8d6c42014-05-07 17:49:08 -040051 private final CircularClipper mClipper;
52 private final H mHandler = new H();
53
54 private int mColumns;
55 private int mCellWidth;
56 private int mCellHeight;
57 private int mLargeCellWidth;
58 private int mLargeCellHeight;
John Spurlock92d9b192014-06-29 12:54:24 -040059 private int mPanelPaddingBottom;
John Spurlock39076ed2014-06-30 20:47:20 -040060 private int mDualTileUnderlap;
John Spurlock5729d092014-05-29 17:42:51 -040061 private boolean mExpanded;
Jorim Jaggie65e3102014-07-01 22:00:50 +020062 private boolean mListening;
John Spurlockaf8d6c42014-05-07 17:49:08 -040063
64 private TileRecord mDetailRecord;
John Spurlock5729d092014-05-29 17:42:51 -040065 private Callback mCallback;
Jorim Jaggi3f48f462014-07-08 16:53:29 +020066 private BrightnessController mBrightnessController;
John Spurlockaf8d6c42014-05-07 17:49:08 -040067
68 public QSPanel(Context context) {
69 this(context, null);
70 }
71
72 public QSPanel(Context context, AttributeSet attrs) {
73 super(context, attrs);
74 mContext = context;
75
John Spurlock7f8f22a2014-07-02 18:54:17 -040076 mDetail = LayoutInflater.from(context).inflate(R.layout.qs_detail, this, false);
77 mDetailContent = (ViewGroup) mDetail.findViewById(android.R.id.content);
78 mDetailSettingsButton = mDetail.findViewById(android.R.id.button2);
79 mDetailDoneButton = mDetail.findViewById(android.R.id.button1);
John Spurlockaf8d6c42014-05-07 17:49:08 -040080 mDetail.setVisibility(GONE);
81 mDetail.setClickable(true);
Jorim Jaggi3f48f462014-07-08 16:53:29 +020082 mBrightnessView = LayoutInflater.from(context).inflate(
83 R.layout.quick_settings_brightness_dialog, this, false);
John Spurlockaf8d6c42014-05-07 17:49:08 -040084 addView(mDetail);
Jorim Jaggi3f48f462014-07-08 16:53:29 +020085 addView(mBrightnessView);
John Spurlockaf8d6c42014-05-07 17:49:08 -040086 mClipper = new CircularClipper(mDetail);
87 updateResources();
Jorim Jaggi3f48f462014-07-08 16:53:29 +020088
89 mBrightnessController = new BrightnessController(getContext(),
90 (ImageView) findViewById(R.id.brightness_icon),
91 (ToggleSlider) findViewById(R.id.brightness_slider));
John Spurlockaf8d6c42014-05-07 17:49:08 -040092 }
93
John Spurlock5729d092014-05-29 17:42:51 -040094 public void setCallback(Callback callback) {
95 mCallback = callback;
96 }
97
John Spurlockaf8d6c42014-05-07 17:49:08 -040098 public void updateResources() {
John Spurlock4bf31982014-05-21 13:04:22 -040099 final Resources res = mContext.getResources();
100 final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
101 mCellHeight = res.getDimensionPixelSize(R.dimen.qs_tile_height);
102 mCellWidth = (int)(mCellHeight * TILE_ASPECT);
103 mLargeCellHeight = res.getDimensionPixelSize(R.dimen.qs_dual_tile_height);
104 mLargeCellWidth = (int)(mLargeCellHeight * TILE_ASPECT);
John Spurlock92d9b192014-06-29 12:54:24 -0400105 mPanelPaddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
John Spurlock39076ed2014-06-30 20:47:20 -0400106 mDualTileUnderlap = res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_vertical);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400107 if (mColumns != columns) {
108 mColumns = columns;
109 postInvalidate();
110 }
111 }
112
113 public void setExpanded(boolean expanded) {
John Spurlock5729d092014-05-29 17:42:51 -0400114 if (mExpanded == expanded) return;
115 mExpanded = expanded;
116 if (!mExpanded) {
John Spurlockaf8d6c42014-05-07 17:49:08 -0400117 showDetail(false /*show*/, mDetailRecord);
118 }
Jorim Jaggie65e3102014-07-01 22:00:50 +0200119 }
120
121 public void setListening(boolean listening) {
122 if (mListening == listening) return;
123 mListening = listening;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400124 for (TileRecord r : mRecords) {
Jorim Jaggie65e3102014-07-01 22:00:50 +0200125 r.tile.setListening(mListening);
126 if (mListening) {
John Spurlockccb6b9a2014-05-17 15:54:40 -0400127 r.tile.refreshState();
128 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400129 }
Jorim Jaggi3f48f462014-07-08 16:53:29 +0200130 if (listening) {
131 mBrightnessController.registerCallbacks();
132 } else {
133 mBrightnessController.unregisterCallbacks();
134 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400135 }
136
137 private void showDetail(boolean show, TileRecord r) {
138 mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
139 }
140
141 private void setTileVisibility(View v, boolean visible) {
142 mHandler.obtainMessage(H.SET_TILE_VISIBILITY, visible ? 1 : 0, 0, v).sendToTarget();
143 }
144
145 private void handleSetTileVisibility(View v, boolean visible) {
John Spurlock360e15b2014-07-08 18:45:21 -0400146 if (visible == (v.getVisibility() == VISIBLE)) return;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400147 v.setVisibility(visible ? VISIBLE : GONE);
148 }
149
150 public void addTile(final QSTile<?> tile) {
151 final TileRecord r = new TileRecord();
152 r.tile = tile;
153 r.tileView = tile.createTileView(mContext);
154 r.tileView.setVisibility(View.GONE);
155 r.tile.setCallback(new QSTile.Callback() {
156 @Override
157 public void onStateChanged(QSTile.State state) {
158 setTileVisibility(r.tileView, state.visible);
159 r.tileView.onStateChanged(state);
160 }
161 @Override
162 public void onShowDetail(boolean show) {
163 QSPanel.this.showDetail(show, r);
164 }
John Spurlock7f8f22a2014-07-02 18:54:17 -0400165 @Override
166 public void onToggleStateChanged(boolean state) {
167 if (mDetailRecord == r) {
168 fireToggleStateChanged(state);
169 }
170 }
John Spurlock486b78e2014-07-07 08:37:56 -0400171 @Override
172 public void onScanStateChanged(boolean state) {
173 if (mDetailRecord == r) {
174 fireScanStateChanged(state);
175 }
176 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400177 });
178 final View.OnClickListener click = new View.OnClickListener() {
179 @Override
180 public void onClick(View v) {
181 r.tile.click();
182 }
183 };
184 final View.OnClickListener clickSecondary = new View.OnClickListener() {
185 @Override
186 public void onClick(View v) {
187 r.tile.secondaryClick();
188 }
189 };
190 r.tileView.init(click, clickSecondary);
John Spurlockccb6b9a2014-05-17 15:54:40 -0400191 r.tile.refreshState();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400192 mRecords.add(r);
193
194 addView(r.tileView);
195 }
196
197 private void handleShowDetail(TileRecord r, boolean show) {
John Spurlock9dcfe062014-05-31 12:54:58 -0400198 if (r == null) return;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400199 AnimatorListener listener = null;
200 if (show) {
John Spurlock7f8f22a2014-07-02 18:54:17 -0400201 if (mDetailRecord != null) return; // already showing something in detail
202 r.detailAdapter = r.tile.getDetailAdapter();
203 if (r.detailAdapter == null) return;
John Spurlock486b78e2014-07-07 08:37:56 -0400204 mDetailRecord = r;
John Spurlock7f8f22a2014-07-02 18:54:17 -0400205 r.detailView = r.detailAdapter.createDetailView(mContext, r.detailView, mDetailContent);
206 if (r.detailView == null) throw new IllegalStateException("Must return detail view");
207 mDetailDoneButton.setOnClickListener(new OnClickListener() {
208 @Override
209 public void onClick(View v) {
210 showDetail(false, mDetailRecord);
211 }
212 });
213 final Intent settingsIntent = r.detailAdapter.getSettingsIntent();
214 mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
215 mDetailSettingsButton.setOnClickListener(new OnClickListener() {
216 @Override
217 public void onClick(View v) {
218 mDetailRecord.tile.mHost.startSettingsActivity(settingsIntent);
219 }
220 });
John Spurlock7f8f22a2014-07-02 18:54:17 -0400221 mDetailContent.removeAllViews();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400222 mDetail.bringToFront();
John Spurlock7f8f22a2014-07-02 18:54:17 -0400223 mDetailContent.addView(r.detailView);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400224 } else {
225 if (mDetailRecord == null) return;
226 listener = mTeardownDetailWhenDone;
227 }
John Spurlock7f8f22a2014-07-02 18:54:17 -0400228 fireShowingDetail(show ? r.detailAdapter : null);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400229 int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
230 int y = r.tileView.getTop() + r.tileView.getHeight() / 2;
231 mClipper.animateCircularClip(x, y, show, listener);
232 }
233
234 @Override
235 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
236 final int width = MeasureSpec.getSize(widthMeasureSpec);
Jorim Jaggi3f48f462014-07-08 16:53:29 +0200237 mBrightnessView.measure(exactly(width), MeasureSpec.UNSPECIFIED);
John Spurlockccb6b9a2014-05-17 15:54:40 -0400238 int r = -1;
239 int c = -1;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400240 int rows = 0;
John Spurlockccb6b9a2014-05-17 15:54:40 -0400241 boolean rowIsDual = false;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400242 for (TileRecord record : mRecords) {
243 if (record.tileView.getVisibility() == GONE) continue;
John Spurlockccb6b9a2014-05-17 15:54:40 -0400244 // wrap to next column if we've reached the max # of columns
245 // also don't allow dual + single tiles on the same row
246 if (r == -1 || c == (mColumns - 1) || rowIsDual != record.tile.supportsDualTargets()) {
247 r++;
248 c = 0;
249 rowIsDual = record.tile.supportsDualTargets();
250 } else {
251 c++;
252 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400253 record.row = r;
254 record.col = c;
255 rows = r + 1;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400256 }
257
258 for (TileRecord record : mRecords) {
259 if (record.tileView.getVisibility() == GONE) continue;
John Spurlockccb6b9a2014-05-17 15:54:40 -0400260 record.tileView.setDual(record.tile.supportsDualTargets());
John Spurlockaf8d6c42014-05-07 17:49:08 -0400261 final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
262 final int ch = record.row == 0 ? mLargeCellHeight : mCellHeight;
263 record.tileView.measure(exactly(cw), exactly(ch));
264 }
Jorim Jaggi3f48f462014-07-08 16:53:29 +0200265 int h = rows == 0 ? mBrightnessView.getHeight() : (getRowTop(rows) + mPanelPaddingBottom);
John Spurlock7f8f22a2014-07-02 18:54:17 -0400266 mDetail.measure(exactly(width), exactly(h));
John Spurlock2684d5e2014-05-28 20:27:44 -0400267 setMeasuredDimension(width, h);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400268 }
269
270 private static int exactly(int size) {
271 return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
272 }
273
274 @Override
275 protected void onLayout(boolean changed, int l, int t, int r, int b) {
John Spurlock4bf31982014-05-21 13:04:22 -0400276 final int w = getWidth();
Jorim Jaggi3f48f462014-07-08 16:53:29 +0200277 mBrightnessView.layout(0, 0,
278 mBrightnessView.getMeasuredWidth(), mBrightnessView.getMeasuredHeight());
John Spurlockaf8d6c42014-05-07 17:49:08 -0400279 for (TileRecord record : mRecords) {
280 if (record.tileView.getVisibility() == GONE) continue;
281 final int cols = getColumnCount(record.row);
282 final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
283 final int extra = (w - cw * cols) / (cols + 1);
284 final int left = record.col * cw + (record.col + 1) * extra;
285 final int top = getRowTop(record.row);
286 record.tileView.layout(left, top,
287 left + record.tileView.getMeasuredWidth(),
288 top + record.tileView.getMeasuredHeight());
289 }
John Spurlock3e04cc82014-05-30 12:34:03 -0400290 final int dh = Math.max(mDetail.getMeasuredHeight(), getMeasuredHeight());
291 mDetail.layout(0, 0, mDetail.getMeasuredWidth(), dh);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400292 }
293
294 private int getRowTop(int row) {
Jorim Jaggi3f48f462014-07-08 16:53:29 +0200295 if (row <= 0) return mBrightnessView.getHeight();
296 return mBrightnessView.getHeight()
297 + mLargeCellHeight - mDualTileUnderlap + (row - 1) * mCellHeight;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400298 }
299
300 private int getColumnCount(int row) {
301 int cols = 0;
302 for (TileRecord record : mRecords) {
303 if (record.tileView.getVisibility() == GONE) continue;
304 if (record.row == row) cols++;
305 }
306 return cols;
307 }
308
John Spurlock7f8f22a2014-07-02 18:54:17 -0400309 private void fireShowingDetail(QSTile.DetailAdapter detail) {
John Spurlock5729d092014-05-29 17:42:51 -0400310 if (mCallback != null) {
John Spurlock7f8f22a2014-07-02 18:54:17 -0400311 mCallback.onShowingDetail(detail);
312 }
313 }
314
315 private void fireToggleStateChanged(boolean state) {
316 if (mCallback != null) {
317 mCallback.onToggleStateChanged(state);
John Spurlock5729d092014-05-29 17:42:51 -0400318 }
319 }
320
John Spurlock486b78e2014-07-07 08:37:56 -0400321 private void fireScanStateChanged(boolean state) {
322 if (mCallback != null) {
323 mCallback.onScanStateChanged(state);
324 }
325 }
326
John Spurlockaf8d6c42014-05-07 17:49:08 -0400327 private class H extends Handler {
328 private static final int SHOW_DETAIL = 1;
329 private static final int SET_TILE_VISIBILITY = 2;
330 @Override
331 public void handleMessage(Message msg) {
332 if (msg.what == SHOW_DETAIL) {
333 handleShowDetail((TileRecord)msg.obj, msg.arg1 != 0);
334 } else if (msg.what == SET_TILE_VISIBILITY) {
335 handleSetTileVisibility((View)msg.obj, msg.arg1 != 0);
336 }
337 }
338 }
339
340 private static final class TileRecord {
341 QSTile<?> tile;
342 QSTileView tileView;
John Spurlock856edeb2014-06-01 20:36:47 -0400343 View detailView;
John Spurlock7f8f22a2014-07-02 18:54:17 -0400344 DetailAdapter detailAdapter;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400345 int row;
346 int col;
347 }
348
349 private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() {
350 public void onAnimationEnd(Animator animation) {
John Spurlock7f8f22a2014-07-02 18:54:17 -0400351 mDetailContent.removeAllViews();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400352 mDetailRecord = null;
353 };
354 };
John Spurlock5729d092014-05-29 17:42:51 -0400355
356 public interface Callback {
John Spurlock7f8f22a2014-07-02 18:54:17 -0400357 void onShowingDetail(QSTile.DetailAdapter detail);
358 void onToggleStateChanged(boolean state);
John Spurlock486b78e2014-07-07 08:37:56 -0400359 void onScanStateChanged(boolean state);
John Spurlock5729d092014-05-29 17:42:51 -0400360 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400361}