blob: 36cd388973f76a17717053b014d3feffabf69d22 [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 Spurlockaf8d6c42014-05-07 17:49:08 -0400171 });
172 final View.OnClickListener click = new View.OnClickListener() {
173 @Override
174 public void onClick(View v) {
175 r.tile.click();
176 }
177 };
178 final View.OnClickListener clickSecondary = new View.OnClickListener() {
179 @Override
180 public void onClick(View v) {
181 r.tile.secondaryClick();
182 }
183 };
184 r.tileView.init(click, clickSecondary);
John Spurlockccb6b9a2014-05-17 15:54:40 -0400185 r.tile.refreshState();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400186 mRecords.add(r);
187
188 addView(r.tileView);
189 }
190
191 private void handleShowDetail(TileRecord r, boolean show) {
John Spurlock9dcfe062014-05-31 12:54:58 -0400192 if (r == null) return;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400193 AnimatorListener listener = null;
194 if (show) {
John Spurlock7f8f22a2014-07-02 18:54:17 -0400195 if (mDetailRecord != null) return; // already showing something in detail
196 r.detailAdapter = r.tile.getDetailAdapter();
197 if (r.detailAdapter == null) return;
198 r.detailView = r.detailAdapter.createDetailView(mContext, r.detailView, mDetailContent);
199 if (r.detailView == null) throw new IllegalStateException("Must return detail view");
200 mDetailDoneButton.setOnClickListener(new OnClickListener() {
201 @Override
202 public void onClick(View v) {
203 showDetail(false, mDetailRecord);
204 }
205 });
206 final Intent settingsIntent = r.detailAdapter.getSettingsIntent();
207 mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
208 mDetailSettingsButton.setOnClickListener(new OnClickListener() {
209 @Override
210 public void onClick(View v) {
211 mDetailRecord.tile.mHost.startSettingsActivity(settingsIntent);
212 }
213 });
John Spurlockaf8d6c42014-05-07 17:49:08 -0400214 mDetailRecord = r;
John Spurlock7f8f22a2014-07-02 18:54:17 -0400215 mDetailContent.removeAllViews();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400216 mDetail.bringToFront();
John Spurlock7f8f22a2014-07-02 18:54:17 -0400217 mDetailContent.addView(r.detailView);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400218 } else {
219 if (mDetailRecord == null) return;
220 listener = mTeardownDetailWhenDone;
221 }
John Spurlock7f8f22a2014-07-02 18:54:17 -0400222 fireShowingDetail(show ? r.detailAdapter : null);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400223 int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
224 int y = r.tileView.getTop() + r.tileView.getHeight() / 2;
225 mClipper.animateCircularClip(x, y, show, listener);
226 }
227
228 @Override
229 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
230 final int width = MeasureSpec.getSize(widthMeasureSpec);
Jorim Jaggi3f48f462014-07-08 16:53:29 +0200231 mBrightnessView.measure(exactly(width), MeasureSpec.UNSPECIFIED);
John Spurlockccb6b9a2014-05-17 15:54:40 -0400232 int r = -1;
233 int c = -1;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400234 int rows = 0;
John Spurlockccb6b9a2014-05-17 15:54:40 -0400235 boolean rowIsDual = false;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400236 for (TileRecord record : mRecords) {
237 if (record.tileView.getVisibility() == GONE) continue;
John Spurlockccb6b9a2014-05-17 15:54:40 -0400238 // wrap to next column if we've reached the max # of columns
239 // also don't allow dual + single tiles on the same row
240 if (r == -1 || c == (mColumns - 1) || rowIsDual != record.tile.supportsDualTargets()) {
241 r++;
242 c = 0;
243 rowIsDual = record.tile.supportsDualTargets();
244 } else {
245 c++;
246 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400247 record.row = r;
248 record.col = c;
249 rows = r + 1;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400250 }
251
252 for (TileRecord record : mRecords) {
253 if (record.tileView.getVisibility() == GONE) continue;
John Spurlockccb6b9a2014-05-17 15:54:40 -0400254 record.tileView.setDual(record.tile.supportsDualTargets());
John Spurlockaf8d6c42014-05-07 17:49:08 -0400255 final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
256 final int ch = record.row == 0 ? mLargeCellHeight : mCellHeight;
257 record.tileView.measure(exactly(cw), exactly(ch));
258 }
Jorim Jaggi3f48f462014-07-08 16:53:29 +0200259 int h = rows == 0 ? mBrightnessView.getHeight() : (getRowTop(rows) + mPanelPaddingBottom);
John Spurlock7f8f22a2014-07-02 18:54:17 -0400260 mDetail.measure(exactly(width), exactly(h));
John Spurlock2684d5e2014-05-28 20:27:44 -0400261 setMeasuredDimension(width, h);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400262 }
263
264 private static int exactly(int size) {
265 return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
266 }
267
268 @Override
269 protected void onLayout(boolean changed, int l, int t, int r, int b) {
John Spurlock4bf31982014-05-21 13:04:22 -0400270 final int w = getWidth();
Jorim Jaggi3f48f462014-07-08 16:53:29 +0200271 mBrightnessView.layout(0, 0,
272 mBrightnessView.getMeasuredWidth(), mBrightnessView.getMeasuredHeight());
John Spurlockaf8d6c42014-05-07 17:49:08 -0400273 for (TileRecord record : mRecords) {
274 if (record.tileView.getVisibility() == GONE) continue;
275 final int cols = getColumnCount(record.row);
276 final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
277 final int extra = (w - cw * cols) / (cols + 1);
278 final int left = record.col * cw + (record.col + 1) * extra;
279 final int top = getRowTop(record.row);
280 record.tileView.layout(left, top,
281 left + record.tileView.getMeasuredWidth(),
282 top + record.tileView.getMeasuredHeight());
283 }
John Spurlock3e04cc82014-05-30 12:34:03 -0400284 final int dh = Math.max(mDetail.getMeasuredHeight(), getMeasuredHeight());
285 mDetail.layout(0, 0, mDetail.getMeasuredWidth(), dh);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400286 }
287
288 private int getRowTop(int row) {
Jorim Jaggi3f48f462014-07-08 16:53:29 +0200289 if (row <= 0) return mBrightnessView.getHeight();
290 return mBrightnessView.getHeight()
291 + mLargeCellHeight - mDualTileUnderlap + (row - 1) * mCellHeight;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400292 }
293
294 private int getColumnCount(int row) {
295 int cols = 0;
296 for (TileRecord record : mRecords) {
297 if (record.tileView.getVisibility() == GONE) continue;
298 if (record.row == row) cols++;
299 }
300 return cols;
301 }
302
John Spurlock7f8f22a2014-07-02 18:54:17 -0400303 private void fireShowingDetail(QSTile.DetailAdapter detail) {
John Spurlock5729d092014-05-29 17:42:51 -0400304 if (mCallback != null) {
John Spurlock7f8f22a2014-07-02 18:54:17 -0400305 mCallback.onShowingDetail(detail);
306 }
307 }
308
309 private void fireToggleStateChanged(boolean state) {
310 if (mCallback != null) {
311 mCallback.onToggleStateChanged(state);
John Spurlock5729d092014-05-29 17:42:51 -0400312 }
313 }
314
John Spurlockaf8d6c42014-05-07 17:49:08 -0400315 private class H extends Handler {
316 private static final int SHOW_DETAIL = 1;
317 private static final int SET_TILE_VISIBILITY = 2;
318 @Override
319 public void handleMessage(Message msg) {
320 if (msg.what == SHOW_DETAIL) {
321 handleShowDetail((TileRecord)msg.obj, msg.arg1 != 0);
322 } else if (msg.what == SET_TILE_VISIBILITY) {
323 handleSetTileVisibility((View)msg.obj, msg.arg1 != 0);
324 }
325 }
326 }
327
328 private static final class TileRecord {
329 QSTile<?> tile;
330 QSTileView tileView;
John Spurlock856edeb2014-06-01 20:36:47 -0400331 View detailView;
John Spurlock7f8f22a2014-07-02 18:54:17 -0400332 DetailAdapter detailAdapter;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400333 int row;
334 int col;
335 }
336
337 private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() {
338 public void onAnimationEnd(Animator animation) {
John Spurlock7f8f22a2014-07-02 18:54:17 -0400339 mDetailContent.removeAllViews();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400340 mDetailRecord = null;
341 };
342 };
John Spurlock5729d092014-05-29 17:42:51 -0400343
344 public interface Callback {
John Spurlock7f8f22a2014-07-02 18:54:17 -0400345 void onShowingDetail(QSTile.DetailAdapter detail);
346 void onToggleStateChanged(boolean state);
John Spurlock5729d092014-05-29 17:42:51 -0400347 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400348}