blob: 72474b8519b89fbd42a2c964856b1b0d61fa1222 [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 }
John Spurlock1a462c12014-07-14 10:52:01 -0400111 if (mListening) {
112 refreshAllTiles();
113 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400114 }
115
116 public void setExpanded(boolean expanded) {
John Spurlock5729d092014-05-29 17:42:51 -0400117 if (mExpanded == expanded) return;
118 mExpanded = expanded;
119 if (!mExpanded) {
John Spurlockaf8d6c42014-05-07 17:49:08 -0400120 showDetail(false /*show*/, mDetailRecord);
121 }
Jorim Jaggie65e3102014-07-01 22:00:50 +0200122 }
123
124 public void setListening(boolean listening) {
125 if (mListening == listening) return;
126 mListening = listening;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400127 for (TileRecord r : mRecords) {
Jorim Jaggie65e3102014-07-01 22:00:50 +0200128 r.tile.setListening(mListening);
John Spurlock1a462c12014-07-14 10:52:01 -0400129 }
130 if (mListening) {
131 refreshAllTiles();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400132 }
Jorim Jaggi3f48f462014-07-08 16:53:29 +0200133 if (listening) {
134 mBrightnessController.registerCallbacks();
135 } else {
136 mBrightnessController.unregisterCallbacks();
137 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400138 }
139
John Spurlock1a462c12014-07-14 10:52:01 -0400140 private void refreshAllTiles() {
141 for (TileRecord r : mRecords) {
142 r.tile.refreshState();
143 }
144 }
145
John Spurlockaf8d6c42014-05-07 17:49:08 -0400146 private void showDetail(boolean show, TileRecord r) {
147 mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
148 }
149
150 private void setTileVisibility(View v, boolean visible) {
151 mHandler.obtainMessage(H.SET_TILE_VISIBILITY, visible ? 1 : 0, 0, v).sendToTarget();
152 }
153
154 private void handleSetTileVisibility(View v, boolean visible) {
John Spurlock360e15b2014-07-08 18:45:21 -0400155 if (visible == (v.getVisibility() == VISIBLE)) return;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400156 v.setVisibility(visible ? VISIBLE : GONE);
157 }
158
159 public void addTile(final QSTile<?> tile) {
160 final TileRecord r = new TileRecord();
161 r.tile = tile;
162 r.tileView = tile.createTileView(mContext);
163 r.tileView.setVisibility(View.GONE);
164 r.tile.setCallback(new QSTile.Callback() {
165 @Override
166 public void onStateChanged(QSTile.State state) {
167 setTileVisibility(r.tileView, state.visible);
168 r.tileView.onStateChanged(state);
169 }
170 @Override
171 public void onShowDetail(boolean show) {
172 QSPanel.this.showDetail(show, r);
173 }
John Spurlock7f8f22a2014-07-02 18:54:17 -0400174 @Override
175 public void onToggleStateChanged(boolean state) {
176 if (mDetailRecord == r) {
177 fireToggleStateChanged(state);
178 }
179 }
John Spurlock486b78e2014-07-07 08:37:56 -0400180 @Override
181 public void onScanStateChanged(boolean state) {
182 if (mDetailRecord == r) {
183 fireScanStateChanged(state);
184 }
185 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400186 });
187 final View.OnClickListener click = new View.OnClickListener() {
188 @Override
189 public void onClick(View v) {
190 r.tile.click();
191 }
192 };
193 final View.OnClickListener clickSecondary = new View.OnClickListener() {
194 @Override
195 public void onClick(View v) {
196 r.tile.secondaryClick();
197 }
198 };
199 r.tileView.init(click, clickSecondary);
John Spurlockccb6b9a2014-05-17 15:54:40 -0400200 r.tile.refreshState();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400201 mRecords.add(r);
202
203 addView(r.tileView);
204 }
205
206 private void handleShowDetail(TileRecord r, boolean show) {
John Spurlock9dcfe062014-05-31 12:54:58 -0400207 if (r == null) return;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400208 AnimatorListener listener = null;
209 if (show) {
John Spurlock7f8f22a2014-07-02 18:54:17 -0400210 if (mDetailRecord != null) return; // already showing something in detail
211 r.detailAdapter = r.tile.getDetailAdapter();
212 if (r.detailAdapter == null) return;
John Spurlock486b78e2014-07-07 08:37:56 -0400213 mDetailRecord = r;
John Spurlock7f8f22a2014-07-02 18:54:17 -0400214 r.detailView = r.detailAdapter.createDetailView(mContext, r.detailView, mDetailContent);
215 if (r.detailView == null) throw new IllegalStateException("Must return detail view");
216 mDetailDoneButton.setOnClickListener(new OnClickListener() {
217 @Override
218 public void onClick(View v) {
219 showDetail(false, mDetailRecord);
220 }
221 });
222 final Intent settingsIntent = r.detailAdapter.getSettingsIntent();
223 mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
224 mDetailSettingsButton.setOnClickListener(new OnClickListener() {
225 @Override
226 public void onClick(View v) {
227 mDetailRecord.tile.mHost.startSettingsActivity(settingsIntent);
228 }
229 });
John Spurlock7f8f22a2014-07-02 18:54:17 -0400230 mDetailContent.removeAllViews();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400231 mDetail.bringToFront();
John Spurlock7f8f22a2014-07-02 18:54:17 -0400232 mDetailContent.addView(r.detailView);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400233 } else {
234 if (mDetailRecord == null) return;
235 listener = mTeardownDetailWhenDone;
236 }
John Spurlock7f8f22a2014-07-02 18:54:17 -0400237 fireShowingDetail(show ? r.detailAdapter : null);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400238 int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
239 int y = r.tileView.getTop() + r.tileView.getHeight() / 2;
240 mClipper.animateCircularClip(x, y, show, listener);
241 }
242
243 @Override
244 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
245 final int width = MeasureSpec.getSize(widthMeasureSpec);
Jorim Jaggi3f48f462014-07-08 16:53:29 +0200246 mBrightnessView.measure(exactly(width), MeasureSpec.UNSPECIFIED);
John Spurlockccb6b9a2014-05-17 15:54:40 -0400247 int r = -1;
248 int c = -1;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400249 int rows = 0;
John Spurlockccb6b9a2014-05-17 15:54:40 -0400250 boolean rowIsDual = false;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400251 for (TileRecord record : mRecords) {
252 if (record.tileView.getVisibility() == GONE) continue;
John Spurlockccb6b9a2014-05-17 15:54:40 -0400253 // wrap to next column if we've reached the max # of columns
254 // also don't allow dual + single tiles on the same row
255 if (r == -1 || c == (mColumns - 1) || rowIsDual != record.tile.supportsDualTargets()) {
256 r++;
257 c = 0;
258 rowIsDual = record.tile.supportsDualTargets();
259 } else {
260 c++;
261 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400262 record.row = r;
263 record.col = c;
264 rows = r + 1;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400265 }
266
267 for (TileRecord record : mRecords) {
268 if (record.tileView.getVisibility() == GONE) continue;
John Spurlockccb6b9a2014-05-17 15:54:40 -0400269 record.tileView.setDual(record.tile.supportsDualTargets());
John Spurlockaf8d6c42014-05-07 17:49:08 -0400270 final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
271 final int ch = record.row == 0 ? mLargeCellHeight : mCellHeight;
272 record.tileView.measure(exactly(cw), exactly(ch));
273 }
Jorim Jaggi3f48f462014-07-08 16:53:29 +0200274 int h = rows == 0 ? mBrightnessView.getHeight() : (getRowTop(rows) + mPanelPaddingBottom);
John Spurlock7f8f22a2014-07-02 18:54:17 -0400275 mDetail.measure(exactly(width), exactly(h));
John Spurlock2684d5e2014-05-28 20:27:44 -0400276 setMeasuredDimension(width, h);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400277 }
278
279 private static int exactly(int size) {
280 return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
281 }
282
283 @Override
284 protected void onLayout(boolean changed, int l, int t, int r, int b) {
John Spurlock4bf31982014-05-21 13:04:22 -0400285 final int w = getWidth();
Jorim Jaggi3f48f462014-07-08 16:53:29 +0200286 mBrightnessView.layout(0, 0,
287 mBrightnessView.getMeasuredWidth(), mBrightnessView.getMeasuredHeight());
John Spurlockaf8d6c42014-05-07 17:49:08 -0400288 for (TileRecord record : mRecords) {
289 if (record.tileView.getVisibility() == GONE) continue;
290 final int cols = getColumnCount(record.row);
291 final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
292 final int extra = (w - cw * cols) / (cols + 1);
293 final int left = record.col * cw + (record.col + 1) * extra;
294 final int top = getRowTop(record.row);
295 record.tileView.layout(left, top,
296 left + record.tileView.getMeasuredWidth(),
297 top + record.tileView.getMeasuredHeight());
298 }
John Spurlock3e04cc82014-05-30 12:34:03 -0400299 final int dh = Math.max(mDetail.getMeasuredHeight(), getMeasuredHeight());
300 mDetail.layout(0, 0, mDetail.getMeasuredWidth(), dh);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400301 }
302
303 private int getRowTop(int row) {
Jorim Jaggi3f48f462014-07-08 16:53:29 +0200304 if (row <= 0) return mBrightnessView.getHeight();
305 return mBrightnessView.getHeight()
306 + mLargeCellHeight - mDualTileUnderlap + (row - 1) * mCellHeight;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400307 }
308
309 private int getColumnCount(int row) {
310 int cols = 0;
311 for (TileRecord record : mRecords) {
312 if (record.tileView.getVisibility() == GONE) continue;
313 if (record.row == row) cols++;
314 }
315 return cols;
316 }
317
John Spurlock7f8f22a2014-07-02 18:54:17 -0400318 private void fireShowingDetail(QSTile.DetailAdapter detail) {
John Spurlock5729d092014-05-29 17:42:51 -0400319 if (mCallback != null) {
John Spurlock7f8f22a2014-07-02 18:54:17 -0400320 mCallback.onShowingDetail(detail);
321 }
322 }
323
324 private void fireToggleStateChanged(boolean state) {
325 if (mCallback != null) {
326 mCallback.onToggleStateChanged(state);
John Spurlock5729d092014-05-29 17:42:51 -0400327 }
328 }
329
John Spurlock486b78e2014-07-07 08:37:56 -0400330 private void fireScanStateChanged(boolean state) {
331 if (mCallback != null) {
332 mCallback.onScanStateChanged(state);
333 }
334 }
335
John Spurlockaf8d6c42014-05-07 17:49:08 -0400336 private class H extends Handler {
337 private static final int SHOW_DETAIL = 1;
338 private static final int SET_TILE_VISIBILITY = 2;
339 @Override
340 public void handleMessage(Message msg) {
341 if (msg.what == SHOW_DETAIL) {
342 handleShowDetail((TileRecord)msg.obj, msg.arg1 != 0);
343 } else if (msg.what == SET_TILE_VISIBILITY) {
344 handleSetTileVisibility((View)msg.obj, msg.arg1 != 0);
345 }
346 }
347 }
348
349 private static final class TileRecord {
350 QSTile<?> tile;
351 QSTileView tileView;
John Spurlock856edeb2014-06-01 20:36:47 -0400352 View detailView;
John Spurlock7f8f22a2014-07-02 18:54:17 -0400353 DetailAdapter detailAdapter;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400354 int row;
355 int col;
356 }
357
358 private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() {
359 public void onAnimationEnd(Animator animation) {
John Spurlock7f8f22a2014-07-02 18:54:17 -0400360 mDetailContent.removeAllViews();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400361 mDetailRecord = null;
362 };
363 };
John Spurlock5729d092014-05-29 17:42:51 -0400364
365 public interface Callback {
John Spurlock7f8f22a2014-07-02 18:54:17 -0400366 void onShowingDetail(QSTile.DetailAdapter detail);
367 void onToggleStateChanged(boolean state);
John Spurlock486b78e2014-07-07 08:37:56 -0400368 void onScanStateChanged(boolean state);
John Spurlock5729d092014-05-29 17:42:51 -0400369 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400370}