blob: afb54836b2e8ef87e2d7ed3aaafc219c014caad9 [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) {
83 r.tile.setShown(expanded);
84 }
85 }
86
87 private void showDetail(boolean show, TileRecord r) {
88 mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
89 }
90
91 private void setTileVisibility(View v, boolean visible) {
92 mHandler.obtainMessage(H.SET_TILE_VISIBILITY, visible ? 1 : 0, 0, v).sendToTarget();
93 }
94
95 private void handleSetTileVisibility(View v, boolean visible) {
96 v.setVisibility(visible ? VISIBLE : GONE);
97 }
98
99 public void addTile(final QSTile<?> tile) {
100 final TileRecord r = new TileRecord();
101 r.tile = tile;
102 r.tileView = tile.createTileView(mContext);
103 r.tileView.setVisibility(View.GONE);
104 r.tile.setCallback(new QSTile.Callback() {
105 @Override
106 public void onStateChanged(QSTile.State state) {
107 setTileVisibility(r.tileView, state.visible);
108 r.tileView.onStateChanged(state);
109 }
110 @Override
111 public void onShowDetail(boolean show) {
112 QSPanel.this.showDetail(show, r);
113 }
114 });
115 final View.OnClickListener click = new View.OnClickListener() {
116 @Override
117 public void onClick(View v) {
118 r.tile.click();
119 }
120 };
121 final View.OnClickListener clickSecondary = new View.OnClickListener() {
122 @Override
123 public void onClick(View v) {
124 r.tile.secondaryClick();
125 }
126 };
127 r.tileView.init(click, clickSecondary);
128 mRecords.add(r);
129
130 addView(r.tileView);
131 }
132
133 private void handleShowDetail(TileRecord r, boolean show) {
134 AnimatorListener listener = null;
135 if (show) {
136 if (mDetailRecord != null) return;
137 final View detail = r.tile.createDetailView(mContext, mDetail);
138 if (detail == null) return;
139 mDetailRecord = r;
140 mDetail.removeAllViews();
141 mDetail.bringToFront();
142 mDetail.addView(detail);
143 } else {
144 if (mDetailRecord == null) return;
145 listener = mTeardownDetailWhenDone;
146 }
147 int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
148 int y = r.tileView.getTop() + r.tileView.getHeight() / 2;
149 mClipper.animateCircularClip(x, y, show, listener);
150 }
151
152 @Override
153 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
154 final int width = MeasureSpec.getSize(widthMeasureSpec);
155 mCellWidth = width / mColumns;
156 mCellHeight = (int)(mCellWidth / TILE_ASPECT);
157 mLargeCellWidth = (int)(mCellWidth * LARGE_TILE_FACTOR);
158 mLargeCellHeight = (int)(mCellHeight * LARGE_TILE_FACTOR);
159 int r = 0;
160 int c = 0;
161 int rows = 0;
162 for (TileRecord record : mRecords) {
163 if (record.tileView.getVisibility() == GONE) continue;
164 record.row = r;
165 record.col = c;
166 rows = r + 1;
167 c++;
168 if (c == mColumns /*end of normal column*/ || r == 0 && c == 2 /*end of 1st column*/) {
169 c = 0;
170 r++;
171 }
172 }
173
174 for (TileRecord record : mRecords) {
175 if (record.tileView.getVisibility() == GONE) continue;
176 record.tileView.setDual(record.row == 0);
177 final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
178 final int ch = record.row == 0 ? mLargeCellHeight : mCellHeight;
179 record.tileView.measure(exactly(cw), exactly(ch));
180 }
181 final int actualHeight = rows == 0 ? 0 : getRowTop(rows);
182 mDetail.measure(exactly(width), exactly(actualHeight));
183 setMeasuredDimension(width, actualHeight);
184 }
185
186 private static int exactly(int size) {
187 return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
188 }
189
190 @Override
191 protected void onLayout(boolean changed, int l, int t, int r, int b) {
192 final int w = mCellWidth * mColumns;
193 for (TileRecord record : mRecords) {
194 if (record.tileView.getVisibility() == GONE) continue;
195 final int cols = getColumnCount(record.row);
196 final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
197 final int extra = (w - cw * cols) / (cols + 1);
198 final int left = record.col * cw + (record.col + 1) * extra;
199 final int top = getRowTop(record.row);
200 record.tileView.layout(left, top,
201 left + record.tileView.getMeasuredWidth(),
202 top + record.tileView.getMeasuredHeight());
203 }
204 mDetail.layout(0, 0, mDetail.getMeasuredWidth(), mDetail.getMeasuredHeight());
205 }
206
207 private int getRowTop(int row) {
208 if (row <= 0) return 0;
209 return mLargeCellHeight + (row - 1) * mCellHeight;
210 }
211
212 private int getColumnCount(int row) {
213 int cols = 0;
214 for (TileRecord record : mRecords) {
215 if (record.tileView.getVisibility() == GONE) continue;
216 if (record.row == row) cols++;
217 }
218 return cols;
219 }
220
221 private class H extends Handler {
222 private static final int SHOW_DETAIL = 1;
223 private static final int SET_TILE_VISIBILITY = 2;
224 @Override
225 public void handleMessage(Message msg) {
226 if (msg.what == SHOW_DETAIL) {
227 handleShowDetail((TileRecord)msg.obj, msg.arg1 != 0);
228 } else if (msg.what == SET_TILE_VISIBILITY) {
229 handleSetTileVisibility((View)msg.obj, msg.arg1 != 0);
230 }
231 }
232 }
233
234 private static final class TileRecord {
235 QSTile<?> tile;
236 QSTileView tileView;
237 int row;
238 int col;
239 }
240
241 private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() {
242 public void onAnimationEnd(Animator animation) {
243 mDetail.removeAllViews();
244 mDetailRecord = null;
245 };
246 };
247}