blob: d5c90d0668bb23fe70ebe0234343bda15ee3a486 [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.content.Context;
Jorim Jaggie17c4b42014-08-26 17:27:31 +020020import android.content.res.Configuration;
John Spurlockaf8d6c42014-05-07 17:49:08 -040021import android.content.res.Resources;
22import android.content.res.TypedArray;
23import android.graphics.Typeface;
24import android.graphics.drawable.Drawable;
John Spurlock4bf31982014-05-21 13:04:22 -040025import android.graphics.drawable.RippleDrawable;
John Spurlockaf8d6c42014-05-07 17:49:08 -040026import android.os.Handler;
27import android.os.Looper;
28import android.os.Message;
Jorim Jaggie17c4b42014-08-26 17:27:31 +020029import android.util.MathUtils;
John Spurlockaf8d6c42014-05-07 17:49:08 -040030import android.util.TypedValue;
31import android.view.Gravity;
32import android.view.View;
33import android.view.ViewGroup;
John Spurlock83e28482014-05-18 17:20:05 -040034import android.widget.ImageView;
John Spurlockaf8d6c42014-05-07 17:49:08 -040035import android.widget.ImageView.ScaleType;
36import android.widget.TextView;
37
Jorim Jaggie17c4b42014-08-26 17:27:31 +020038import com.android.systemui.FontSizeUtils;
John Spurlockaf8d6c42014-05-07 17:49:08 -040039import com.android.systemui.R;
40import com.android.systemui.qs.QSTile.State;
41
42/** View that represents a standard quick settings tile. **/
43public class QSTileView extends ViewGroup {
44 private static final Typeface CONDENSED = Typeface.create("sans-serif-condensed",
45 Typeface.NORMAL);
John Spurlockaf8d6c42014-05-07 17:49:08 -040046
47 protected final Context mContext;
48 private final View mIcon;
49 private final View mDivider;
John Spurlockaf8d6c42014-05-07 17:49:08 -040050 private final H mHandler = new H();
John Spurlock4bf31982014-05-21 13:04:22 -040051 private final int mIconSizePx;
John Spurlock92d9b192014-06-29 12:54:24 -040052 private final int mTileSpacingPx;
Jorim Jaggie17c4b42014-08-26 17:27:31 +020053 private int mTilePaddingTopPx;
John Spurlock92d9b192014-06-29 12:54:24 -040054 private final int mTilePaddingBelowIconPx;
John Spurlock39076ed2014-06-30 20:47:20 -040055 private final int mDualTileVerticalPaddingPx;
Selim Cineke32010a2014-08-20 23:50:41 +020056 private final View mTopBackgroundView;
John Spurlockaf8d6c42014-05-07 17:49:08 -040057
John Spurlockd47a3f32014-05-18 19:14:14 -040058 private TextView mLabel;
John Spurlock92d9b192014-06-29 12:54:24 -040059 private QSDualTileLabel mDualLabel;
John Spurlockaf8d6c42014-05-07 17:49:08 -040060 private boolean mDual;
61 private OnClickListener mClickPrimary;
62 private OnClickListener mClickSecondary;
John Spurlock4bf31982014-05-21 13:04:22 -040063 private RippleDrawable mRipple;
John Spurlockaf8d6c42014-05-07 17:49:08 -040064
65 public QSTileView(Context context) {
66 super(context);
67
68 mContext = context;
69 final Resources res = context.getResources();
John Spurlock4bf31982014-05-21 13:04:22 -040070 mIconSizePx = res.getDimensionPixelSize(R.dimen.qs_tile_icon_size);
John Spurlock92d9b192014-06-29 12:54:24 -040071 mTileSpacingPx = res.getDimensionPixelSize(R.dimen.qs_tile_spacing);
John Spurlock92d9b192014-06-29 12:54:24 -040072 mTilePaddingBelowIconPx = res.getDimensionPixelSize(R.dimen.qs_tile_padding_below_icon);
John Spurlock39076ed2014-06-30 20:47:20 -040073 mDualTileVerticalPaddingPx =
74 res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_vertical);
John Spurlockd47a3f32014-05-18 19:14:14 -040075 recreateLabel();
John Spurlockaf8d6c42014-05-07 17:49:08 -040076 setClipChildren(false);
77
Selim Cineke32010a2014-08-20 23:50:41 +020078 mTopBackgroundView = new View(context);
79 addView(mTopBackgroundView);
80
John Spurlockaf8d6c42014-05-07 17:49:08 -040081 mIcon = createIcon();
82 addView(mIcon);
83
84 mDivider = new View(mContext);
John Spurlock4bf31982014-05-21 13:04:22 -040085 mDivider.setBackgroundColor(res.getColor(R.color.qs_tile_divider));
86 final int dh = res.getDimensionPixelSize(R.dimen.qs_tile_divider_height);
John Spurlockaf8d6c42014-05-07 17:49:08 -040087 mDivider.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, dh));
88 addView(mDivider);
89
90 setClickable(true);
Jorim Jaggie17c4b42014-08-26 17:27:31 +020091
92 updateTopPadding();
93 }
94
95 private void updateTopPadding() {
96 Resources res = getResources();
97 int padding = res.getDimensionPixelSize(R.dimen.qs_tile_padding_top);
98 int largePadding = res.getDimensionPixelSize(R.dimen.qs_tile_padding_top_large_text);
99 float largeFactor = (MathUtils.constrain(getResources().getConfiguration().fontScale,
100 1.0f, FontSizeUtils.LARGE_TEXT_SCALE) - 1f) / (FontSizeUtils.LARGE_TEXT_SCALE - 1f);
101 mTilePaddingTopPx = Math.round((1 - largeFactor) * padding + largeFactor * largePadding);
102 requestLayout();
103 }
104
105 @Override
106 protected void onConfigurationChanged(Configuration newConfig) {
107 super.onConfigurationChanged(newConfig);
108 updateTopPadding();
109 FontSizeUtils.updateFontSize(mLabel, R.dimen.qs_tile_text_size);
110 if (mDualLabel != null) {
111 mDualLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
112 getResources().getDimensionPixelSize(R.dimen.qs_tile_text_size));
113 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400114 }
115
John Spurlockd47a3f32014-05-18 19:14:14 -0400116 private void recreateLabel() {
117 CharSequence labelText = null;
Selim Cineke5557a92014-08-15 19:59:23 +0200118 CharSequence labelDescription = null;
John Spurlockd47a3f32014-05-18 19:14:14 -0400119 if (mLabel != null) {
120 labelText = mLabel.getText();
121 removeView(mLabel);
John Spurlock92d9b192014-06-29 12:54:24 -0400122 mLabel = null;
123 }
124 if (mDualLabel != null) {
125 labelText = mDualLabel.getText();
Selim Cineke5557a92014-08-15 19:59:23 +0200126 labelDescription = mLabel.getContentDescription();
John Spurlock92d9b192014-06-29 12:54:24 -0400127 removeView(mDualLabel);
128 mDualLabel = null;
John Spurlockd47a3f32014-05-18 19:14:14 -0400129 }
130 final Resources res = mContext.getResources();
John Spurlock92d9b192014-06-29 12:54:24 -0400131 if (mDual) {
John Spurlock39076ed2014-06-30 20:47:20 -0400132 mDualLabel = new QSDualTileLabel(mContext);
John Spurlock92d9b192014-06-29 12:54:24 -0400133 mDualLabel.setId(android.R.id.title);
John Spurlock39076ed2014-06-30 20:47:20 -0400134 mDualLabel.setBackgroundResource(R.drawable.btn_borderless_rect);
Jason Monk5d325072014-10-27 11:38:47 -0400135 mDualLabel.setFirstLineCaret(res.getDrawable(R.drawable.qs_dual_tile_caret));
John Spurlock92d9b192014-06-29 12:54:24 -0400136 mDualLabel.setTextColor(res.getColor(R.color.qs_tile_text));
John Spurlock39076ed2014-06-30 20:47:20 -0400137 mDualLabel.setPadding(0, mDualTileVerticalPaddingPx, 0, mDualTileVerticalPaddingPx);
John Spurlock92d9b192014-06-29 12:54:24 -0400138 mDualLabel.setTypeface(CONDENSED);
139 mDualLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
140 res.getDimensionPixelSize(R.dimen.qs_tile_text_size));
141 mDualLabel.setClickable(true);
142 mDualLabel.setOnClickListener(mClickSecondary);
Selim Cineke32010a2014-08-20 23:50:41 +0200143 mDualLabel.setFocusable(true);
John Spurlock92d9b192014-06-29 12:54:24 -0400144 if (labelText != null) {
145 mDualLabel.setText(labelText);
146 }
Selim Cineke5557a92014-08-15 19:59:23 +0200147 if (labelDescription != null) {
148 mDualLabel.setContentDescription(labelDescription);
149 }
John Spurlock92d9b192014-06-29 12:54:24 -0400150 addView(mDualLabel);
151 } else {
152 mLabel = new TextView(mContext);
153 mLabel.setId(android.R.id.title);
154 mLabel.setTextColor(res.getColor(R.color.qs_tile_text));
155 mLabel.setGravity(Gravity.CENTER_HORIZONTAL);
156 mLabel.setMinLines(2);
157 mLabel.setPadding(0, 0, 0, 0);
158 mLabel.setTypeface(CONDENSED);
159 mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
160 res.getDimensionPixelSize(R.dimen.qs_tile_text_size));
161 mLabel.setClickable(false);
162 if (labelText != null) {
163 mLabel.setText(labelText);
164 }
165 addView(mLabel);
John Spurlockd47a3f32014-05-18 19:14:14 -0400166 }
John Spurlockd47a3f32014-05-18 19:14:14 -0400167 }
168
John Spurlockaf8d6c42014-05-07 17:49:08 -0400169 public void setDual(boolean dual) {
John Spurlockd47a3f32014-05-18 19:14:14 -0400170 final boolean changed = dual != mDual;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400171 mDual = dual;
John Spurlockd47a3f32014-05-18 19:14:14 -0400172 if (changed) {
173 recreateLabel();
174 }
Selim Cineke32010a2014-08-20 23:50:41 +0200175 Drawable tileBackground = getTileBackground();
176 if (tileBackground instanceof RippleDrawable) {
177 setRipple((RippleDrawable) tileBackground);
178 }
179 if (dual) {
180 mTopBackgroundView.setOnClickListener(mClickPrimary);
181 setOnClickListener(null);
182 setClickable(false);
183 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
184 mTopBackgroundView.setBackground(tileBackground);
185 } else {
186 mTopBackgroundView.setOnClickListener(null);
187 mTopBackgroundView.setClickable(false);
188 setOnClickListener(mClickPrimary);
189 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
190 setBackground(tileBackground);
191 }
192 mTopBackgroundView.setFocusable(dual);
193 setFocusable(!dual);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400194 mDivider.setVisibility(dual ? VISIBLE : GONE);
195 postInvalidate();
196 }
197
Selim Cineke32010a2014-08-20 23:50:41 +0200198 private void setRipple(RippleDrawable tileBackground) {
199 mRipple = tileBackground;
200 if (getWidth() != 0) {
201 updateRippleSize(getWidth(), getHeight());
202 }
203 }
204
John Spurlockaf8d6c42014-05-07 17:49:08 -0400205 public void init(OnClickListener clickPrimary, OnClickListener clickSecondary) {
206 mClickPrimary = clickPrimary;
207 mClickSecondary = clickSecondary;
208 }
209
210 protected View createIcon() {
John Spurlock83e28482014-05-18 17:20:05 -0400211 final ImageView icon = new ImageView(mContext);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400212 icon.setId(android.R.id.icon);
213 icon.setScaleType(ScaleType.CENTER_INSIDE);
214 return icon;
215 }
216
John Spurlock4bf31982014-05-21 13:04:22 -0400217 private Drawable getTileBackground() {
John Spurlock856edeb2014-06-01 20:36:47 -0400218 final int[] attrs = new int[] { android.R.attr.selectableItemBackgroundBorderless };
John Spurlockaf8d6c42014-05-07 17:49:08 -0400219 final TypedArray ta = mContext.obtainStyledAttributes(attrs);
220 final Drawable d = ta.getDrawable(0);
221 ta.recycle();
222 return d;
223 }
224
John Spurlock92d9b192014-06-29 12:54:24 -0400225 private View labelView() {
226 return mDual ? mDualLabel : mLabel;
227 }
228
John Spurlockaf8d6c42014-05-07 17:49:08 -0400229 @Override
230 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
231 final int w = MeasureSpec.getSize(widthMeasureSpec);
232 final int h = MeasureSpec.getSize(heightMeasureSpec);
John Spurlock4bf31982014-05-21 13:04:22 -0400233 final int iconSpec = exactly(mIconSizePx);
Jorim Jaggic737b9b2014-09-08 23:57:20 +0200234 mIcon.measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.AT_MOST), iconSpec);
John Spurlock92d9b192014-06-29 12:54:24 -0400235 labelView().measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(h, MeasureSpec.AT_MOST));
John Spurlockaf8d6c42014-05-07 17:49:08 -0400236 if (mDual) {
237 mDivider.measure(widthMeasureSpec, exactly(mDivider.getLayoutParams().height));
238 }
Selim Cineke32010a2014-08-20 23:50:41 +0200239 int heightSpec = exactly(
240 mIconSizePx + mTilePaddingBelowIconPx + mTilePaddingTopPx);
241 mTopBackgroundView.measure(widthMeasureSpec, heightSpec);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400242 setMeasuredDimension(w, h);
243 }
244
245 private static int exactly(int size) {
246 return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
247 }
248
249 @Override
250 protected void onLayout(boolean changed, int l, int t, int r, int b) {
251 final int w = getMeasuredWidth();
John Spurlock529aa732014-07-10 22:49:16 -0400252 final int h = getMeasuredHeight();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400253
Selim Cineke32010a2014-08-20 23:50:41 +0200254 layout(mTopBackgroundView, 0, mTileSpacingPx);
255
John Spurlock92d9b192014-06-29 12:54:24 -0400256 int top = 0;
257 top += mTileSpacingPx;
258 top += mTilePaddingTopPx;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400259 final int iconLeft = (w - mIcon.getMeasuredWidth()) / 2;
260 layout(mIcon, iconLeft, top);
John Spurlock4bf31982014-05-21 13:04:22 -0400261 if (mRipple != null) {
Selim Cineke32010a2014-08-20 23:50:41 +0200262 updateRippleSize(w, h);
263
John Spurlock4bf31982014-05-21 13:04:22 -0400264 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400265 top = mIcon.getBottom();
John Spurlock92d9b192014-06-29 12:54:24 -0400266 top += mTilePaddingBelowIconPx;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400267 if (mDual) {
John Spurlockaf8d6c42014-05-07 17:49:08 -0400268 layout(mDivider, 0, top);
269 top = mDivider.getBottom();
270 }
John Spurlock92d9b192014-06-29 12:54:24 -0400271 layout(labelView(), 0, top);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400272 }
273
Selim Cineke32010a2014-08-20 23:50:41 +0200274 private void updateRippleSize(int width, int height) {
275 // center the touch feedback on the center of the icon, and dial it down a bit
276 final int cx = width / 2;
277 final int cy = mDual ? mIcon.getTop() + mIcon.getHeight() / 2 : height / 2;
278 final int rad = (int)(mIcon.getHeight() * 1.25f);
279 mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad);
280 }
281
John Spurlockaf8d6c42014-05-07 17:49:08 -0400282 private static void layout(View child, int left, int top) {
283 child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
284 }
285
286 protected void handleStateChanged(QSTile.State state) {
John Spurlock83e28482014-05-18 17:20:05 -0400287 if (mIcon instanceof ImageView) {
288 ImageView iv = (ImageView) mIcon;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400289 if (state.icon != null) {
John Spurlock83e28482014-05-18 17:20:05 -0400290 iv.setImageDrawable(state.icon);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400291 } else if (state.iconId > 0) {
John Spurlock83e28482014-05-18 17:20:05 -0400292 iv.setImageResource(state.iconId);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400293 }
Selim Cinek06d3bca2014-08-26 17:29:20 +0200294 Drawable drawable = iv.getDrawable();
295 if (state.autoMirrorDrawable && drawable != null) {
296 drawable.setAutoMirrored(true);
297 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400298 }
John Spurlock92d9b192014-06-29 12:54:24 -0400299 if (mDual) {
300 mDualLabel.setText(state.label);
Selim Cineke5557a92014-08-15 19:59:23 +0200301 mDualLabel.setContentDescription(state.dualLabelContentDescription);
Jason Monkec81d382014-09-18 16:11:09 -0400302 mTopBackgroundView.setContentDescription(state.contentDescription);
John Spurlock92d9b192014-06-29 12:54:24 -0400303 } else {
304 mLabel.setText(state.label);
Jason Monkec81d382014-09-18 16:11:09 -0400305 setContentDescription(state.contentDescription);
John Spurlock92d9b192014-06-29 12:54:24 -0400306 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400307 }
308
309 public void onStateChanged(QSTile.State state) {
310 mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget();
311 }
312
313 private class H extends Handler {
314 private static final int STATE_CHANGED = 1;
315 public H() {
316 super(Looper.getMainLooper());
317 }
318 @Override
319 public void handleMessage(Message msg) {
320 if (msg.what == STATE_CHANGED) {
321 handleStateChanged((State) msg.obj);
322 }
323 }
324 }
Selim Cinekece4a832014-08-22 15:35:53 +0000325}