blob: 6176eb65f1038228c397a39f7a3dd347a10c135f [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;
23import android.os.Handler;
24import android.os.Message;
25import android.util.AttributeSet;
26import android.view.View;
27import android.view.ViewGroup;
28import android.widget.FrameLayout;
29
30import com.android.systemui.R;
31
32import java.util.ArrayList;
33
34/** View that represents the quick settings tile panel. **/
35public class QSPanel extends ViewGroup {
36 private static final float TILE_ASPECT = 1.4f;
37 private static final float LARGE_TILE_FACTOR = 1.1f;
38
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;
50
51 private TileRecord mDetailRecord;
52
53 public QSPanel(Context context) {
54 this(context, null);
55 }
56
57 public QSPanel(Context context, AttributeSet attrs) {
58 super(context, attrs);
59 mContext = context;
60
61 mDetail = new FrameLayout(mContext);
62 mDetail.setVisibility(GONE);
63 mDetail.setClickable(true);
64 addView(mDetail);
65 mClipper = new CircularClipper(mDetail);
66 updateResources();
67 }
68
69 public void updateResources() {
70 final int columns = Math.max(1,
71 mContext.getResources().getInteger(R.integer.quick_settings_num_columns));
72 if (mColumns != columns) {
73 mColumns = columns;
74 postInvalidate();
75 }
76 }
77
78 public void setExpanded(boolean expanded) {
79 if (!expanded) {
80 showDetail(false /*show*/, mDetailRecord);
81 }
82 for (TileRecord r : mRecords) {
John Spurlockccb6b9a2014-05-17 15:54:40 -040083 r.tile.setListening(expanded);
84 if (expanded) {
85 r.tile.refreshState();
86 }
John Spurlockaf8d6c42014-05-07 17:49:08 -040087 }
88 }
89
90 private void showDetail(boolean show, TileRecord r) {
91 mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
92 }
93
94 private void setTileVisibility(View v, boolean visible) {
95 mHandler.obtainMessage(H.SET_TILE_VISIBILITY, visible ? 1 : 0, 0, v).sendToTarget();
96 }
97
98 private void handleSetTileVisibility(View v, boolean visible) {
99 v.setVisibility(visible ? VISIBLE : GONE);
100 }
101
102 public void addTile(final QSTile<?> tile) {
103 final TileRecord r = new TileRecord();
104 r.tile = tile;
105 r.tileView = tile.createTileView(mContext);
106 r.tileView.setVisibility(View.GONE);
107 r.tile.setCallback(new QSTile.Callback() {
108 @Override
109 public void onStateChanged(QSTile.State state) {
110 setTileVisibility(r.tileView, state.visible);
111 r.tileView.onStateChanged(state);
112 }
113 @Override
114 public void onShowDetail(boolean show) {
115 QSPanel.this.showDetail(show, r);
116 }
117 });
118 final View.OnClickListener click = new View.OnClickListener() {
119 @Override
120 public void onClick(View v) {
121 r.tile.click();
122 }
123 };
124 final View.OnClickListener clickSecondary = new View.OnClickListener() {
125 @Override
126 public void onClick(View v) {
127 r.tile.secondaryClick();
128 }
129 };
130 r.tileView.init(click, clickSecondary);
John Spurlockccb6b9a2014-05-17 15:54:40 -0400131 r.tile.refreshState();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400132 mRecords.add(r);
133
134 addView(r.tileView);
135 }
136
137 private void handleShowDetail(TileRecord r, boolean show) {
138 AnimatorListener listener = null;
139 if (show) {
140 if (mDetailRecord != null) return;
141 final View detail = r.tile.createDetailView(mContext, mDetail);
142 if (detail == null) return;
143 mDetailRecord = r;
144 mDetail.removeAllViews();
145 mDetail.bringToFront();
146 mDetail.addView(detail);
147 } else {
148 if (mDetailRecord == null) return;
149 listener = mTeardownDetailWhenDone;
150 }
151 int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
152 int y = r.tileView.getTop() + r.tileView.getHeight() / 2;
153 mClipper.animateCircularClip(x, y, show, listener);
154 }
155
156 @Override
157 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
158 final int width = MeasureSpec.getSize(widthMeasureSpec);
159 mCellWidth = width / mColumns;
160 mCellHeight = (int)(mCellWidth / TILE_ASPECT);
161 mLargeCellWidth = (int)(mCellWidth * LARGE_TILE_FACTOR);
162 mLargeCellHeight = (int)(mCellHeight * LARGE_TILE_FACTOR);
John Spurlockccb6b9a2014-05-17 15:54:40 -0400163 int r = -1;
164 int c = -1;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400165 int rows = 0;
John Spurlockccb6b9a2014-05-17 15:54:40 -0400166 boolean rowIsDual = false;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400167 for (TileRecord record : mRecords) {
168 if (record.tileView.getVisibility() == GONE) continue;
John Spurlockccb6b9a2014-05-17 15:54:40 -0400169 // wrap to next column if we've reached the max # of columns
170 // also don't allow dual + single tiles on the same row
171 if (r == -1 || c == (mColumns - 1) || rowIsDual != record.tile.supportsDualTargets()) {
172 r++;
173 c = 0;
174 rowIsDual = record.tile.supportsDualTargets();
175 } else {
176 c++;
177 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400178 record.row = r;
179 record.col = c;
180 rows = r + 1;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400181 }
182
183 for (TileRecord record : mRecords) {
184 if (record.tileView.getVisibility() == GONE) continue;
John Spurlockccb6b9a2014-05-17 15:54:40 -0400185 record.tileView.setDual(record.tile.supportsDualTargets());
John Spurlockaf8d6c42014-05-07 17:49:08 -0400186 final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
187 final int ch = record.row == 0 ? mLargeCellHeight : mCellHeight;
188 record.tileView.measure(exactly(cw), exactly(ch));
189 }
190 final int actualHeight = rows == 0 ? 0 : getRowTop(rows);
191 mDetail.measure(exactly(width), exactly(actualHeight));
192 setMeasuredDimension(width, actualHeight);
193 }
194
195 private static int exactly(int size) {
196 return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
197 }
198
199 @Override
200 protected void onLayout(boolean changed, int l, int t, int r, int b) {
201 final int w = mCellWidth * mColumns;
202 for (TileRecord record : mRecords) {
203 if (record.tileView.getVisibility() == GONE) continue;
204 final int cols = getColumnCount(record.row);
205 final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
206 final int extra = (w - cw * cols) / (cols + 1);
207 final int left = record.col * cw + (record.col + 1) * extra;
208 final int top = getRowTop(record.row);
209 record.tileView.layout(left, top,
210 left + record.tileView.getMeasuredWidth(),
211 top + record.tileView.getMeasuredHeight());
212 }
213 mDetail.layout(0, 0, mDetail.getMeasuredWidth(), mDetail.getMeasuredHeight());
214 }
215
216 private int getRowTop(int row) {
217 if (row <= 0) return 0;
218 return mLargeCellHeight + (row - 1) * mCellHeight;
219 }
220
221 private int getColumnCount(int row) {
222 int cols = 0;
223 for (TileRecord record : mRecords) {
224 if (record.tileView.getVisibility() == GONE) continue;
225 if (record.row == row) cols++;
226 }
227 return cols;
228 }
229
230 private class H extends Handler {
231 private static final int SHOW_DETAIL = 1;
232 private static final int SET_TILE_VISIBILITY = 2;
233 @Override
234 public void handleMessage(Message msg) {
235 if (msg.what == SHOW_DETAIL) {
236 handleShowDetail((TileRecord)msg.obj, msg.arg1 != 0);
237 } else if (msg.what == SET_TILE_VISIBILITY) {
238 handleSetTileVisibility((View)msg.obj, msg.arg1 != 0);
239 }
240 }
241 }
242
243 private static final class TileRecord {
244 QSTile<?> tile;
245 QSTileView tileView;
246 int row;
247 int col;
248 }
249
250 private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() {
251 public void onAnimationEnd(Animator animation) {
252 mDetail.removeAllViews();
253 mDetailRecord = null;
254 };
255 };
256}