blob: 6ce0e48e96653b13b5fb33c3d9b949a30d7a761f [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 Spurlock5729d092014-05-29 17:42:51 -040050 private boolean mExpanded;
John Spurlockaf8d6c42014-05-07 17:49:08 -040051
52 private TileRecord mDetailRecord;
John Spurlock5729d092014-05-29 17:42:51 -040053 private Callback mCallback;
John Spurlockaf8d6c42014-05-07 17:49:08 -040054
55 public QSPanel(Context context) {
56 this(context, null);
57 }
58
59 public QSPanel(Context context, AttributeSet attrs) {
60 super(context, attrs);
61 mContext = context;
62
63 mDetail = new FrameLayout(mContext);
64 mDetail.setVisibility(GONE);
65 mDetail.setClickable(true);
66 addView(mDetail);
67 mClipper = new CircularClipper(mDetail);
68 updateResources();
69 }
70
John Spurlock5729d092014-05-29 17:42:51 -040071 public void setCallback(Callback callback) {
72 mCallback = callback;
73 }
74
John Spurlockaf8d6c42014-05-07 17:49:08 -040075 public void updateResources() {
John Spurlock4bf31982014-05-21 13:04:22 -040076 final Resources res = mContext.getResources();
77 final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
78 mCellHeight = res.getDimensionPixelSize(R.dimen.qs_tile_height);
79 mCellWidth = (int)(mCellHeight * TILE_ASPECT);
80 mLargeCellHeight = res.getDimensionPixelSize(R.dimen.qs_dual_tile_height);
81 mLargeCellWidth = (int)(mLargeCellHeight * TILE_ASPECT);
John Spurlockaf8d6c42014-05-07 17:49:08 -040082 if (mColumns != columns) {
83 mColumns = columns;
84 postInvalidate();
85 }
86 }
87
John Spurlock4bf31982014-05-21 13:04:22 -040088 public void setUtils(CircularClipper.Utils utils) {
89 mClipper.setUtils(utils);
90 }
91
John Spurlockaf8d6c42014-05-07 17:49:08 -040092 public void setExpanded(boolean expanded) {
John Spurlock5729d092014-05-29 17:42:51 -040093 if (mExpanded == expanded) return;
94 mExpanded = expanded;
95 if (!mExpanded) {
John Spurlockaf8d6c42014-05-07 17:49:08 -040096 showDetail(false /*show*/, mDetailRecord);
97 }
98 for (TileRecord r : mRecords) {
John Spurlock5729d092014-05-29 17:42:51 -040099 r.tile.setListening(mExpanded);
100 if (mExpanded) {
John Spurlockccb6b9a2014-05-17 15:54:40 -0400101 r.tile.refreshState();
102 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400103 }
104 }
105
106 private void showDetail(boolean show, TileRecord r) {
107 mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
108 }
109
110 private void setTileVisibility(View v, boolean visible) {
111 mHandler.obtainMessage(H.SET_TILE_VISIBILITY, visible ? 1 : 0, 0, v).sendToTarget();
112 }
113
114 private void handleSetTileVisibility(View v, boolean visible) {
115 v.setVisibility(visible ? VISIBLE : GONE);
116 }
117
118 public void addTile(final QSTile<?> tile) {
119 final TileRecord r = new TileRecord();
120 r.tile = tile;
121 r.tileView = tile.createTileView(mContext);
122 r.tileView.setVisibility(View.GONE);
123 r.tile.setCallback(new QSTile.Callback() {
124 @Override
125 public void onStateChanged(QSTile.State state) {
126 setTileVisibility(r.tileView, state.visible);
127 r.tileView.onStateChanged(state);
128 }
129 @Override
130 public void onShowDetail(boolean show) {
131 QSPanel.this.showDetail(show, r);
132 }
133 });
134 final View.OnClickListener click = new View.OnClickListener() {
135 @Override
136 public void onClick(View v) {
137 r.tile.click();
138 }
139 };
140 final View.OnClickListener clickSecondary = new View.OnClickListener() {
141 @Override
142 public void onClick(View v) {
143 r.tile.secondaryClick();
144 }
145 };
146 r.tileView.init(click, clickSecondary);
John Spurlockccb6b9a2014-05-17 15:54:40 -0400147 r.tile.refreshState();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400148 mRecords.add(r);
149
150 addView(r.tileView);
151 }
152
153 private void handleShowDetail(TileRecord r, boolean show) {
154 AnimatorListener listener = null;
155 if (show) {
156 if (mDetailRecord != null) return;
157 final View detail = r.tile.createDetailView(mContext, mDetail);
158 if (detail == null) return;
159 mDetailRecord = r;
160 mDetail.removeAllViews();
161 mDetail.bringToFront();
162 mDetail.addView(detail);
163 } else {
164 if (mDetailRecord == null) return;
165 listener = mTeardownDetailWhenDone;
166 }
John Spurlock5729d092014-05-29 17:42:51 -0400167 fireShowingDetail(show);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400168 int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
169 int y = r.tileView.getTop() + r.tileView.getHeight() / 2;
170 mClipper.animateCircularClip(x, y, show, listener);
171 }
172
173 @Override
174 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
175 final int width = MeasureSpec.getSize(widthMeasureSpec);
John Spurlockccb6b9a2014-05-17 15:54:40 -0400176 int r = -1;
177 int c = -1;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400178 int rows = 0;
John Spurlockccb6b9a2014-05-17 15:54:40 -0400179 boolean rowIsDual = false;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400180 for (TileRecord record : mRecords) {
181 if (record.tileView.getVisibility() == GONE) continue;
John Spurlockccb6b9a2014-05-17 15:54:40 -0400182 // wrap to next column if we've reached the max # of columns
183 // also don't allow dual + single tiles on the same row
184 if (r == -1 || c == (mColumns - 1) || rowIsDual != record.tile.supportsDualTargets()) {
185 r++;
186 c = 0;
187 rowIsDual = record.tile.supportsDualTargets();
188 } else {
189 c++;
190 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400191 record.row = r;
192 record.col = c;
193 rows = r + 1;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400194 }
195
196 for (TileRecord record : mRecords) {
197 if (record.tileView.getVisibility() == GONE) continue;
John Spurlockccb6b9a2014-05-17 15:54:40 -0400198 record.tileView.setDual(record.tile.supportsDualTargets());
John Spurlockaf8d6c42014-05-07 17:49:08 -0400199 final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
200 final int ch = record.row == 0 ? mLargeCellHeight : mCellHeight;
201 record.tileView.measure(exactly(cw), exactly(ch));
202 }
John Spurlock2684d5e2014-05-28 20:27:44 -0400203 int h = rows == 0 ? 0 : getRowTop(rows);
204 mDetail.measure(exactly(width), unspecified());
205 if (mDetail.getVisibility() == VISIBLE && mDetail.getChildCount() > 0) {
206 final int dmh = mDetail.getMeasuredHeight();
207 if (dmh > 0) h = dmh;
208 }
209 setMeasuredDimension(width, h);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400210 }
211
212 private static int exactly(int size) {
213 return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
214 }
215
John Spurlock2684d5e2014-05-28 20:27:44 -0400216 private static int unspecified() {
217 return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
218 }
219
John Spurlockaf8d6c42014-05-07 17:49:08 -0400220 @Override
221 protected void onLayout(boolean changed, int l, int t, int r, int b) {
John Spurlock4bf31982014-05-21 13:04:22 -0400222 final int w = getWidth();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400223 for (TileRecord record : mRecords) {
224 if (record.tileView.getVisibility() == GONE) continue;
225 final int cols = getColumnCount(record.row);
226 final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
227 final int extra = (w - cw * cols) / (cols + 1);
228 final int left = record.col * cw + (record.col + 1) * extra;
229 final int top = getRowTop(record.row);
230 record.tileView.layout(left, top,
231 left + record.tileView.getMeasuredWidth(),
232 top + record.tileView.getMeasuredHeight());
233 }
234 mDetail.layout(0, 0, mDetail.getMeasuredWidth(), mDetail.getMeasuredHeight());
235 }
236
237 private int getRowTop(int row) {
238 if (row <= 0) return 0;
239 return mLargeCellHeight + (row - 1) * mCellHeight;
240 }
241
242 private int getColumnCount(int row) {
243 int cols = 0;
244 for (TileRecord record : mRecords) {
245 if (record.tileView.getVisibility() == GONE) continue;
246 if (record.row == row) cols++;
247 }
248 return cols;
249 }
250
John Spurlock5729d092014-05-29 17:42:51 -0400251 private void fireShowingDetail(boolean showingDetail) {
252 if (mCallback != null) {
253 mCallback.onShowingDetail(showingDetail);
254 }
255 }
256
John Spurlockaf8d6c42014-05-07 17:49:08 -0400257 private class H extends Handler {
258 private static final int SHOW_DETAIL = 1;
259 private static final int SET_TILE_VISIBILITY = 2;
260 @Override
261 public void handleMessage(Message msg) {
262 if (msg.what == SHOW_DETAIL) {
263 handleShowDetail((TileRecord)msg.obj, msg.arg1 != 0);
264 } else if (msg.what == SET_TILE_VISIBILITY) {
265 handleSetTileVisibility((View)msg.obj, msg.arg1 != 0);
266 }
267 }
268 }
269
270 private static final class TileRecord {
271 QSTile<?> tile;
272 QSTileView tileView;
273 int row;
274 int col;
275 }
276
277 private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() {
278 public void onAnimationEnd(Animator animation) {
279 mDetail.removeAllViews();
280 mDetailRecord = null;
281 };
282 };
John Spurlock5729d092014-05-29 17:42:51 -0400283
284 public interface Callback {
285 void onShowingDetail(boolean showingDetail);
286 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400287}