QSTileView.java revision 8600534df66c2ff5846ed230b50c56229322d48a
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.content.Context;
20import android.content.res.Resources;
21import android.content.res.TypedArray;
22import android.graphics.Typeface;
23import android.graphics.drawable.Drawable;
24import android.graphics.drawable.RippleDrawable;
25import android.os.Handler;
26import android.os.Looper;
27import android.os.Message;
28import android.util.TypedValue;
29import android.view.ContextThemeWrapper;
30import android.view.Gravity;
31import android.view.View;
32import android.view.ViewGroup;
33import android.widget.ImageView;
34import android.widget.ImageView.ScaleType;
35import android.widget.TextView;
36
37import com.android.systemui.R;
38import com.android.systemui.qs.QSTile.State;
39
40/** View that represents a standard quick settings tile. **/
41public class QSTileView extends ViewGroup {
42    private static final Typeface CONDENSED = Typeface.create("sans-serif-condensed",
43            Typeface.NORMAL);
44
45    protected final Context mContext;
46    private final View mIcon;
47    private final View mDivider;
48    private final H mHandler = new H();
49    private final int mIconSizePx;
50
51    private int mTilePaddingPx;
52    private TextView mLabel;
53    private boolean mDual;
54    private OnClickListener mClickPrimary;
55    private OnClickListener mClickSecondary;
56    private RippleDrawable mRipple;
57
58    public QSTileView(Context context) {
59        super(context);
60
61        mContext = context;
62        final Resources res = context.getResources();
63        mIconSizePx = res.getDimensionPixelSize(R.dimen.qs_tile_icon_size);
64        recreateLabel();
65        setClipChildren(false);
66
67        mIcon = createIcon();
68        addView(mIcon);
69
70        mDivider = new View(mContext);
71        mDivider.setBackgroundColor(res.getColor(R.color.qs_tile_divider));
72        final int dh = res.getDimensionPixelSize(R.dimen.qs_tile_divider_height);
73        mDivider.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, dh));
74        addView(mDivider);
75
76        setClickable(true);
77        setBackground(getTileBackground());
78    }
79
80    private void recreateLabel() {
81        CharSequence labelText = null;
82        if (mLabel != null) {
83            labelText = mLabel.getText();
84            removeView(mLabel);
85        }
86        final Resources res = mContext.getResources();
87        mLabel = new TextView(mDual
88                ? new ContextThemeWrapper(mContext, R.style.BorderlessButton_Tiny)
89                : mContext);
90        mLabel.setId(android.R.id.title);
91        mLabel.setTextColor(res.getColor(R.color.qs_tile_text));
92        mLabel.setGravity(Gravity.CENTER_HORIZONTAL);
93        mLabel.setMinLines(2);
94        mTilePaddingPx = res.getDimensionPixelSize(
95                mDual ? R.dimen.qs_dual_tile_padding : R.dimen.qs_tile_padding);
96        final int bottomPadding = mDual ? 0 : mTilePaddingPx;
97        mLabel.setPadding(mTilePaddingPx, mTilePaddingPx, mTilePaddingPx, bottomPadding);
98        mLabel.setTypeface(CONDENSED);
99        mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
100                res.getDimensionPixelSize(R.dimen.qs_tile_text_size));
101        if (labelText != null) {
102            mLabel.setText(labelText);
103        }
104        addView(mLabel);
105    }
106
107    public void setDual(boolean dual) {
108        final boolean changed = dual != mDual;
109        mDual = dual;
110        if (changed) {
111            recreateLabel();
112        }
113        if (mDual) {
114            setOnClickListener(mClickPrimary);
115            mLabel.setClickable(true);
116            mLabel.setOnClickListener(mClickSecondary);
117        } else {
118            mLabel.setClickable(false);
119            setOnClickListener(mClickPrimary);
120        }
121        mDivider.setVisibility(dual ? VISIBLE : GONE);
122        postInvalidate();
123    }
124
125    public void init(OnClickListener clickPrimary, OnClickListener clickSecondary) {
126        mClickPrimary = clickPrimary;
127        mClickSecondary = clickSecondary;
128    }
129
130    protected View createIcon() {
131        final ImageView icon = new ImageView(mContext);
132        icon.setId(android.R.id.icon);
133        icon.setScaleType(ScaleType.CENTER_INSIDE);
134        return icon;
135    }
136
137    private Drawable getTileBackground() {
138        final int[] attrs = new int[] { android.R.attr.selectableItemBackground};
139        final TypedArray ta = mContext.obtainStyledAttributes(attrs);
140        final Drawable d = ta.getDrawable(0);
141        ta.recycle();
142        if (d instanceof RippleDrawable) {
143            mRipple = (RippleDrawable) d;
144        }
145        return d;
146    }
147
148    @Override
149    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
150        final int w = MeasureSpec.getSize(widthMeasureSpec);
151        final int h = MeasureSpec.getSize(heightMeasureSpec);
152        final int iconSpec = exactly(mIconSizePx);
153        mIcon.measure(iconSpec, iconSpec);
154        mLabel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(h, MeasureSpec.AT_MOST));
155        if (mDual) {
156            mDivider.measure(widthMeasureSpec, exactly(mDivider.getLayoutParams().height));
157        }
158        setMeasuredDimension(w, h);
159    }
160
161    private static int exactly(int size) {
162        return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
163    }
164
165    @Override
166    protected void onLayout(boolean changed, int l, int t, int r, int b) {
167        final int w = getMeasuredWidth();
168        final int h = getMeasuredHeight();
169
170        final int contentHeight = mTilePaddingPx + mIcon.getMeasuredHeight()
171                + mLabel.getMeasuredHeight()
172                + (mDual ? (mTilePaddingPx + mDivider.getMeasuredHeight()) : 0);
173
174        int top = Math.max(0, (h - contentHeight) / 2);
175        top += mTilePaddingPx;
176        final int iconLeft = (w - mIcon.getMeasuredWidth()) / 2;
177        layout(mIcon, iconLeft, top);
178        if (mRipple != null) {
179            // center the touch feedback on the center of the icon, and dial it down a bit
180            final int cx = w / 2;
181            final int cy = mIcon.getTop() + mIcon.getHeight() / 2;
182            final int rad = (int)(mIcon.getHeight() * 1.5);
183            mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad);
184        }
185        top = mIcon.getBottom();
186        if (mDual) {
187            top += mTilePaddingPx;
188            layout(mDivider, 0, top);
189            top = mDivider.getBottom();
190        }
191        layout(mLabel, 0, top);
192    }
193
194    private static void layout(View child, int left, int top) {
195        child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
196    }
197
198    protected void handleStateChanged(QSTile.State state) {
199        if (mIcon instanceof ImageView) {
200            ImageView iv = (ImageView) mIcon;
201            if (state.icon != null) {
202                iv.setImageDrawable(state.icon);
203            } else if (state.iconId > 0) {
204                iv.setImageResource(state.iconId);
205            }
206        }
207        mLabel.setText(state.label);
208        setContentDescription(state.contentDescription);
209    }
210
211    public void onStateChanged(QSTile.State state) {
212        mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget();
213    }
214
215    private class H extends Handler {
216        private static final int STATE_CHANGED = 1;
217        public H() {
218            super(Looper.getMainLooper());
219        }
220        @Override
221        public void handleMessage(Message msg) {
222            if (msg.what == STATE_CHANGED) {
223                handleStateChanged((State) msg.obj);
224            }
225        }
226    }
227}