QSPanel.java revision 4bf31983d247a76142937c01bc2800b7f98d0281
1/*
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.content.res.Resources;
24import 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 {
37    private static final float TILE_ASPECT = 1.2f;
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 Resources res = mContext.getResources();
71        final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
72        mCellHeight = res.getDimensionPixelSize(R.dimen.qs_tile_height);
73        mCellWidth = (int)(mCellHeight * TILE_ASPECT);
74        mLargeCellHeight = res.getDimensionPixelSize(R.dimen.qs_dual_tile_height);
75        mLargeCellWidth = (int)(mLargeCellHeight * TILE_ASPECT);
76        if (mColumns != columns) {
77            mColumns = columns;
78            postInvalidate();
79        }
80    }
81
82    public void setUtils(CircularClipper.Utils utils) {
83        mClipper.setUtils(utils);
84    }
85
86    public void setExpanded(boolean expanded) {
87        if (!expanded) {
88            showDetail(false /*show*/, mDetailRecord);
89        }
90        for (TileRecord r : mRecords) {
91            r.tile.setListening(expanded);
92            if (expanded) {
93                r.tile.refreshState();
94            }
95        }
96    }
97
98    private void showDetail(boolean show, TileRecord r) {
99        mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
100    }
101
102    private void setTileVisibility(View v, boolean visible) {
103        mHandler.obtainMessage(H.SET_TILE_VISIBILITY, visible ? 1 : 0, 0, v).sendToTarget();
104    }
105
106    private void handleSetTileVisibility(View v, boolean visible) {
107        v.setVisibility(visible ? VISIBLE : GONE);
108    }
109
110    public void addTile(final QSTile<?> tile) {
111        final TileRecord r = new TileRecord();
112        r.tile = tile;
113        r.tileView = tile.createTileView(mContext);
114        r.tileView.setVisibility(View.GONE);
115        r.tile.setCallback(new QSTile.Callback() {
116            @Override
117            public void onStateChanged(QSTile.State state) {
118                setTileVisibility(r.tileView, state.visible);
119                r.tileView.onStateChanged(state);
120            }
121            @Override
122            public void onShowDetail(boolean show) {
123                QSPanel.this.showDetail(show, r);
124            }
125        });
126        final View.OnClickListener click = new View.OnClickListener() {
127            @Override
128            public void onClick(View v) {
129                r.tile.click();
130            }
131        };
132        final View.OnClickListener clickSecondary = new View.OnClickListener() {
133            @Override
134            public void onClick(View v) {
135                r.tile.secondaryClick();
136            }
137        };
138        r.tileView.init(click, clickSecondary);
139        r.tile.refreshState();
140        mRecords.add(r);
141
142        addView(r.tileView);
143    }
144
145    private void handleShowDetail(TileRecord r, boolean show) {
146        AnimatorListener listener = null;
147        if (show) {
148            if (mDetailRecord != null) return;
149            final View detail = r.tile.createDetailView(mContext, mDetail);
150            if (detail == null) return;
151            mDetailRecord = r;
152            mDetail.removeAllViews();
153            mDetail.bringToFront();
154            mDetail.addView(detail);
155        } else {
156            if (mDetailRecord == null) return;
157            listener = mTeardownDetailWhenDone;
158        }
159        int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
160        int y = r.tileView.getTop() + r.tileView.getHeight() / 2;
161        mClipper.animateCircularClip(x, y, show, listener);
162    }
163
164    @Override
165    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
166        final int width = MeasureSpec.getSize(widthMeasureSpec);
167        int r = -1;
168        int c = -1;
169        int rows = 0;
170        boolean rowIsDual = false;
171        for (TileRecord record : mRecords) {
172            if (record.tileView.getVisibility() == GONE) continue;
173            // wrap to next column if we've reached the max # of columns
174            // also don't allow dual + single tiles on the same row
175            if (r == -1 || c == (mColumns - 1) || rowIsDual != record.tile.supportsDualTargets()) {
176                r++;
177                c = 0;
178                rowIsDual = record.tile.supportsDualTargets();
179            } else {
180                c++;
181            }
182            record.row = r;
183            record.col = c;
184            rows = r + 1;
185        }
186
187        for (TileRecord record : mRecords) {
188            if (record.tileView.getVisibility() == GONE) continue;
189            record.tileView.setDual(record.tile.supportsDualTargets());
190            final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
191            final int ch = record.row == 0 ? mLargeCellHeight : mCellHeight;
192            record.tileView.measure(exactly(cw), exactly(ch));
193        }
194        final int actualHeight = rows == 0 ? 0 : getRowTop(rows);
195        mDetail.measure(exactly(width), exactly(actualHeight));
196        setMeasuredDimension(width, actualHeight);
197    }
198
199    private static int exactly(int size) {
200        return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
201    }
202
203    @Override
204    protected void onLayout(boolean changed, int l, int t, int r, int b) {
205        final int w = getWidth();
206        for (TileRecord record : mRecords) {
207            if (record.tileView.getVisibility() == GONE) continue;
208            final int cols = getColumnCount(record.row);
209            final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth;
210            final int extra = (w - cw * cols) / (cols + 1);
211            final int left = record.col * cw + (record.col + 1) * extra;
212            final int top = getRowTop(record.row);
213            record.tileView.layout(left, top,
214                    left + record.tileView.getMeasuredWidth(),
215                    top + record.tileView.getMeasuredHeight());
216        }
217        mDetail.layout(0, 0, mDetail.getMeasuredWidth(), mDetail.getMeasuredHeight());
218    }
219
220    private int getRowTop(int row) {
221        if (row <= 0) return 0;
222        return mLargeCellHeight + (row - 1) * mCellHeight;
223    }
224
225    private int getColumnCount(int row) {
226        int cols = 0;
227        for (TileRecord record : mRecords) {
228            if (record.tileView.getVisibility() == GONE) continue;
229            if (record.row == row) cols++;
230        }
231        return cols;
232    }
233
234    private class H extends Handler {
235        private static final int SHOW_DETAIL = 1;
236        private static final int SET_TILE_VISIBILITY = 2;
237        @Override
238        public void handleMessage(Message msg) {
239            if (msg.what == SHOW_DETAIL) {
240                handleShowDetail((TileRecord)msg.obj, msg.arg1 != 0);
241            } else if (msg.what == SET_TILE_VISIBILITY) {
242                handleSetTileVisibility((View)msg.obj, msg.arg1 != 0);
243            }
244        }
245    }
246
247    private static final class TileRecord {
248        QSTile<?> tile;
249        QSTileView tileView;
250        int row;
251        int col;
252    }
253
254    private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() {
255        public void onAnimationEnd(Animator animation) {
256            mDetail.removeAllViews();
257            mDetailRecord = null;
258        };
259    };
260}
261