blob: d152887f811b6d42b3951e621f58403b1e2f5a35 [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 Spurlock4bf31982014-05-21 13:04:22 -040023import android.content.res.Resources;
John Spurlockaf8d6c42014-05-07 17:49:08 -040024import android.os.Handler;
25import android.os.Message;
26import android.util.AttributeSet;
27import android.view.View;
28import android.view.ViewGroup;
29import android.widget.FrameLayout;
30
31import com.android.systemui.R;
32
33import java.util.ArrayList;
34
35/** View that represents the quick settings tile panel. **/
36public class QSPanel extends ViewGroup {
John Spurlock4bf31982014-05-21 13:04:22 -040037 private static final float TILE_ASPECT = 1.2f;
John Spurlockaf8d6c42014-05-07 17:49:08 -040038
39 private final Context mContext;
40 private final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
41 private final FrameLayout mDetail;
42 private final CircularClipper mClipper;
43 private final H mHandler = new H();
44
45 private int mColumns;
46 private int mCellWidth;
47 private int mCellHeight;
48 private int mLargeCellWidth;
49 private int mLargeCellHeight;
John Spurlock92d9b192014-06-29 12:54:24 -040050 private int mPanelPaddingBottom;
John Spurlock39076ed2014-06-30 20:47:20 -040051 private int mDualTileUnderlap;
John Spurlock5729d092014-05-29 17:42:51 -040052 private boolean mExpanded;
Jorim Jaggie65e3102014-07-01 22:00:50 +020053 private boolean mListening;
John Spurlockaf8d6c42014-05-07 17:49:08 -040054
55 private TileRecord mDetailRecord;
John Spurlock5729d092014-05-29 17:42:51 -040056 private Callback mCallback;
John Spurlockaf8d6c42014-05-07 17:49:08 -040057
58 public QSPanel(Context context) {
59 this(context, null);
60 }
61
62 public QSPanel(Context context, AttributeSet attrs) {
63 super(context, attrs);
64 mContext = context;
65
66 mDetail = new FrameLayout(mContext);
John Spurlock3e04cc82014-05-30 12:34:03 -040067 mDetail.setBackgroundColor(mContext.getResources().getColor(R.color.system_primary_color));
John Spurlockaf8d6c42014-05-07 17:49:08 -040068 mDetail.setVisibility(GONE);
69 mDetail.setClickable(true);
70 addView(mDetail);
71 mClipper = new CircularClipper(mDetail);
72 updateResources();
73 }
74
John Spurlock5729d092014-05-29 17:42:51 -040075 public void setCallback(Callback callback) {
76 mCallback = callback;
77 }
78
John Spurlockaf8d6c42014-05-07 17:49:08 -040079 public void updateResources() {
John Spurlock4bf31982014-05-21 13:04:22 -040080 final Resources res = mContext.getResources();
81 final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
82 mCellHeight = res.getDimensionPixelSize(R.dimen.qs_tile_height);
83 mCellWidth = (int)(mCellHeight * TILE_ASPECT);
84 mLargeCellHeight = res.getDimensionPixelSize(R.dimen.qs_dual_tile_height);
85 mLargeCellWidth = (int)(mLargeCellHeight * TILE_ASPECT);
John Spurlock92d9b192014-06-29 12:54:24 -040086 mPanelPaddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
John Spurlock39076ed2014-06-30 20:47:20 -040087 mDualTileUnderlap = res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_vertical);
John Spurlockaf8d6c42014-05-07 17:49:08 -040088 if (mColumns != columns) {
89 mColumns = columns;
90 postInvalidate();
91 }
92 }
93
John Spurlock4bf31982014-05-21 13:04:22 -040094 public void setUtils(CircularClipper.Utils utils) {
95 mClipper.setUtils(utils);
96 }
97
John Spurlockaf8d6c42014-05-07 17:49:08 -040098 public void setExpanded(boolean expanded) {
John Spurlock5729d092014-05-29 17:42:51 -040099 if (mExpanded == expanded) return;
100 mExpanded = expanded;
101 if (!mExpanded) {
John Spurlockaf8d6c42014-05-07 17:49:08 -0400102 showDetail(false /*show*/, mDetailRecord);
103 }
Jorim Jaggie65e3102014-07-01 22:00:50 +0200104 }
105
106 public void setListening(boolean listening) {
107 if (mListening == listening) return;
108 mListening = listening;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400109 for (TileRecord r : mRecords) {
Jorim Jaggie65e3102014-07-01 22:00:50 +0200110 r.tile.setListening(mListening);
111 if (mListening) {
John Spurlockccb6b9a2014-05-17 15:54:40 -0400112 r.tile.refreshState();
113 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400114 }
115 }
116
117 private void showDetail(boolean show, TileRecord r) {
118 mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
119 }
120
121 private void setTileVisibility(View v, boolean visible) {
122 mHandler.obtainMessage(H.SET_TILE_VISIBILITY, visible ? 1 : 0, 0, v).sendToTarget();
123 }
124
125 private void handleSetTileVisibility(View v, boolean visible) {
126 v.setVisibility(visible ? VISIBLE : GONE);
127 }
128
129 public void addTile(final QSTile<?> tile) {
130 final TileRecord r = new TileRecord();
131 r.tile = tile;
132 r.tileView = tile.createTileView(mContext);
133 r.tileView.setVisibility(View.GONE);
134 r.tile.setCallback(new QSTile.Callback() {
135 @Override
136 public void onStateChanged(QSTile.State state) {
137 setTileVisibility(r.tileView, state.visible);
138 r.tileView.onStateChanged(state);
139 }
140 @Override
141 public void onShowDetail(boolean show) {
142 QSPanel.this.showDetail(show, r);
143 }
144 });
145 final View.OnClickListener click = new View.OnClickListener() {
146 @Override
147 public void onClick(View v) {
148 r.tile.click();
149 }
150 };
151 final View.OnClickListener clickSecondary = new View.OnClickListener() {
152 @Override
153 public void onClick(View v) {
154 r.tile.secondaryClick();
155 }
156 };
157 r.tileView.init(click, clickSecondary);
John Spurlockccb6b9a2014-05-17 15:54:40 -0400158 r.tile.refreshState();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400159 mRecords.add(r);
160
161 addView(r.tileView);
162 }
163
164 private void handleShowDetail(TileRecord r, boolean show) {
John Spurlock9dcfe062014-05-31 12:54:58 -0400165 if (r == null) return;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400166 AnimatorListener listener = null;
167 if (show) {
168 if (mDetailRecord != null) return;
John Spurlock856edeb2014-06-01 20:36:47 -0400169 if (r.detailView == null) {
170 r.detailView = r.tile.createDetailView(mContext, mDetail);
171 }
172 if (r.detailView == null) return;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400173 mDetailRecord = r;
174 mDetail.removeAllViews();
175 mDetail.bringToFront();
John Spurlock856edeb2014-06-01 20:36:47 -0400176 mDetail.addView(r.detailView);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400177 } else {
178 if (mDetailRecord == null) return;
179 listener = mTeardownDetailWhenDone;
180 }
John Spurlock5729d092014-05-29 17:42:51 -0400181 fireShowingDetail(show);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400182 int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
183 int y = r.tileView.getTop() + r.tileView.getHeight() / 2;
184 mClipper.animateCircularClip(x, y, show, listener);
185 }
186
187 @Override
188 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
189 final int width = MeasureSpec.getSize(widthMeasureSpec);
John Spurlockccb6b9a2014-05-17 15:54:40 -0400190 int r = -1;
191 int c = -1;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400192 int rows = 0;
John Spurlockccb6b9a2014-05-17 15:54:40 -0400193 boolean rowIsDual = false;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400194 for (TileRecord record : mRecords) {
195 if (record.tileView.getVisibility() == GONE) continue;
John Spurlockccb6b9a2014-05-17 15:54:40 -0400196 // wrap to next column if we've reached the max # of columns
197 // also don't allow dual + single tiles on the same row
198 if (r == -1 || c == (mColumns - 1) || rowIsDual != record.tile.supportsDualTargets()) {
199 r++;
200 c = 0;
201 rowIsDual = record.tile.supportsDualTargets();
202 } else {
203 c++;
204 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400205 record.row = r;
206 record.col = c;
207 rows = r + 1;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400208 }
209
210 for (TileRecord record : mRecords) {
211 if (record.tileView.getVisibility() == GONE) continue;
John Spurlockccb6b9a2014-05-17 15:54:40 -0400212 record.tileView.setDual(record.tile.supportsDualTargets());
John Spurlockaf8d6c42014-05-07 17:49:08 -0400213 final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
214 final int ch = record.row == 0 ? mLargeCellHeight : mCellHeight;
215 record.tileView.measure(exactly(cw), exactly(ch));
216 }
John Spurlock92d9b192014-06-29 12:54:24 -0400217 int h = rows == 0 ? 0 : (getRowTop(rows) + mPanelPaddingBottom);
John Spurlock2684d5e2014-05-28 20:27:44 -0400218 mDetail.measure(exactly(width), unspecified());
219 if (mDetail.getVisibility() == VISIBLE && mDetail.getChildCount() > 0) {
220 final int dmh = mDetail.getMeasuredHeight();
John Spurlock3e04cc82014-05-30 12:34:03 -0400221 if (dmh > 0) h = Math.max(h, dmh);
John Spurlock2684d5e2014-05-28 20:27:44 -0400222 }
223 setMeasuredDimension(width, h);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400224 }
225
226 private static int exactly(int size) {
227 return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
228 }
229
John Spurlock2684d5e2014-05-28 20:27:44 -0400230 private static int unspecified() {
231 return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
232 }
233
John Spurlockaf8d6c42014-05-07 17:49:08 -0400234 @Override
235 protected void onLayout(boolean changed, int l, int t, int r, int b) {
John Spurlock4bf31982014-05-21 13:04:22 -0400236 final int w = getWidth();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400237 for (TileRecord record : mRecords) {
238 if (record.tileView.getVisibility() == GONE) continue;
239 final int cols = getColumnCount(record.row);
240 final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
241 final int extra = (w - cw * cols) / (cols + 1);
242 final int left = record.col * cw + (record.col + 1) * extra;
243 final int top = getRowTop(record.row);
244 record.tileView.layout(left, top,
245 left + record.tileView.getMeasuredWidth(),
246 top + record.tileView.getMeasuredHeight());
247 }
John Spurlock3e04cc82014-05-30 12:34:03 -0400248 final int dh = Math.max(mDetail.getMeasuredHeight(), getMeasuredHeight());
249 mDetail.layout(0, 0, mDetail.getMeasuredWidth(), dh);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400250 }
251
252 private int getRowTop(int row) {
253 if (row <= 0) return 0;
John Spurlock39076ed2014-06-30 20:47:20 -0400254 return mLargeCellHeight - mDualTileUnderlap + (row - 1) * mCellHeight;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400255 }
256
257 private int getColumnCount(int row) {
258 int cols = 0;
259 for (TileRecord record : mRecords) {
260 if (record.tileView.getVisibility() == GONE) continue;
261 if (record.row == row) cols++;
262 }
263 return cols;
264 }
265
John Spurlock5729d092014-05-29 17:42:51 -0400266 private void fireShowingDetail(boolean showingDetail) {
267 if (mCallback != null) {
268 mCallback.onShowingDetail(showingDetail);
269 }
270 }
271
John Spurlockaf8d6c42014-05-07 17:49:08 -0400272 private class H extends Handler {
273 private static final int SHOW_DETAIL = 1;
274 private static final int SET_TILE_VISIBILITY = 2;
275 @Override
276 public void handleMessage(Message msg) {
277 if (msg.what == SHOW_DETAIL) {
278 handleShowDetail((TileRecord)msg.obj, msg.arg1 != 0);
279 } else if (msg.what == SET_TILE_VISIBILITY) {
280 handleSetTileVisibility((View)msg.obj, msg.arg1 != 0);
281 }
282 }
283 }
284
285 private static final class TileRecord {
286 QSTile<?> tile;
287 QSTileView tileView;
John Spurlock856edeb2014-06-01 20:36:47 -0400288 View detailView;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400289 int row;
290 int col;
291 }
292
293 private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() {
294 public void onAnimationEnd(Animator animation) {
295 mDetail.removeAllViews();
296 mDetailRecord = null;
297 };
298 };
John Spurlock5729d092014-05-29 17:42:51 -0400299
300 public interface Callback {
301 void onShowingDetail(boolean showingDetail);
302 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400303}