blob: bb353d5f91e9aed413fcfb209cf38bfc5d569a8b [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;
John Spurlock2d695812014-10-30 13:25:21 -040024import android.graphics.drawable.Animatable;
John Spurlockaf8d6c42014-05-07 17:49:08 -040025import android.graphics.drawable.Drawable;
John Spurlock4bf31982014-05-21 13:04:22 -040026import android.graphics.drawable.RippleDrawable;
John Spurlockaf8d6c42014-05-07 17:49:08 -040027import android.os.Handler;
28import android.os.Looper;
29import android.os.Message;
Jorim Jaggie17c4b42014-08-26 17:27:31 +020030import android.util.MathUtils;
John Spurlockaf8d6c42014-05-07 17:49:08 -040031import android.util.TypedValue;
32import android.view.Gravity;
33import android.view.View;
34import android.view.ViewGroup;
John Spurlock83e28482014-05-18 17:20:05 -040035import android.widget.ImageView;
John Spurlockaf8d6c42014-05-07 17:49:08 -040036import android.widget.ImageView.ScaleType;
37import android.widget.TextView;
38
Jorim Jaggie17c4b42014-08-26 17:27:31 +020039import com.android.systemui.FontSizeUtils;
John Spurlockaf8d6c42014-05-07 17:49:08 -040040import com.android.systemui.R;
41import com.android.systemui.qs.QSTile.State;
42
John Spurlock2d695812014-10-30 13:25:21 -040043import java.util.Objects;
44
John Spurlockaf8d6c42014-05-07 17:49:08 -040045/** View that represents a standard quick settings tile. **/
46public class QSTileView extends ViewGroup {
47 private static final Typeface CONDENSED = Typeface.create("sans-serif-condensed",
48 Typeface.NORMAL);
John Spurlockaf8d6c42014-05-07 17:49:08 -040049
50 protected final Context mContext;
51 private final View mIcon;
52 private final View mDivider;
John Spurlockaf8d6c42014-05-07 17:49:08 -040053 private final H mHandler = new H();
John Spurlock4bf31982014-05-21 13:04:22 -040054 private final int mIconSizePx;
John Spurlock92d9b192014-06-29 12:54:24 -040055 private final int mTileSpacingPx;
Jorim Jaggie17c4b42014-08-26 17:27:31 +020056 private int mTilePaddingTopPx;
John Spurlock92d9b192014-06-29 12:54:24 -040057 private final int mTilePaddingBelowIconPx;
John Spurlock39076ed2014-06-30 20:47:20 -040058 private final int mDualTileVerticalPaddingPx;
Selim Cineke32010a2014-08-20 23:50:41 +020059 private final View mTopBackgroundView;
John Spurlockaf8d6c42014-05-07 17:49:08 -040060
John Spurlockd47a3f32014-05-18 19:14:14 -040061 private TextView mLabel;
John Spurlock92d9b192014-06-29 12:54:24 -040062 private QSDualTileLabel mDualLabel;
John Spurlockaf8d6c42014-05-07 17:49:08 -040063 private boolean mDual;
64 private OnClickListener mClickPrimary;
65 private OnClickListener mClickSecondary;
John Spurlockc247b8f2014-11-06 23:06:25 -050066 private OnLongClickListener mLongClick;
John Spurlock31798f12014-11-04 09:41:32 -050067 private Drawable mTileBackground;
John Spurlock4bf31982014-05-21 13:04:22 -040068 private RippleDrawable mRipple;
John Spurlockaf8d6c42014-05-07 17:49:08 -040069
70 public QSTileView(Context context) {
71 super(context);
72
73 mContext = context;
74 final Resources res = context.getResources();
John Spurlock4bf31982014-05-21 13:04:22 -040075 mIconSizePx = res.getDimensionPixelSize(R.dimen.qs_tile_icon_size);
John Spurlock92d9b192014-06-29 12:54:24 -040076 mTileSpacingPx = res.getDimensionPixelSize(R.dimen.qs_tile_spacing);
John Spurlock92d9b192014-06-29 12:54:24 -040077 mTilePaddingBelowIconPx = res.getDimensionPixelSize(R.dimen.qs_tile_padding_below_icon);
John Spurlock39076ed2014-06-30 20:47:20 -040078 mDualTileVerticalPaddingPx =
79 res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_vertical);
John Spurlock31798f12014-11-04 09:41:32 -050080 mTileBackground = newTileBackground();
John Spurlockd47a3f32014-05-18 19:14:14 -040081 recreateLabel();
John Spurlockaf8d6c42014-05-07 17:49:08 -040082 setClipChildren(false);
83
Selim Cineke32010a2014-08-20 23:50:41 +020084 mTopBackgroundView = new View(context);
85 addView(mTopBackgroundView);
86
John Spurlockaf8d6c42014-05-07 17:49:08 -040087 mIcon = createIcon();
88 addView(mIcon);
89
90 mDivider = new View(mContext);
John Spurlock4bf31982014-05-21 13:04:22 -040091 mDivider.setBackgroundColor(res.getColor(R.color.qs_tile_divider));
92 final int dh = res.getDimensionPixelSize(R.dimen.qs_tile_divider_height);
John Spurlockaf8d6c42014-05-07 17:49:08 -040093 mDivider.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, dh));
94 addView(mDivider);
95
96 setClickable(true);
Jorim Jaggie17c4b42014-08-26 17:27:31 +020097
98 updateTopPadding();
99 }
100
101 private void updateTopPadding() {
102 Resources res = getResources();
103 int padding = res.getDimensionPixelSize(R.dimen.qs_tile_padding_top);
104 int largePadding = res.getDimensionPixelSize(R.dimen.qs_tile_padding_top_large_text);
105 float largeFactor = (MathUtils.constrain(getResources().getConfiguration().fontScale,
106 1.0f, FontSizeUtils.LARGE_TEXT_SCALE) - 1f) / (FontSizeUtils.LARGE_TEXT_SCALE - 1f);
107 mTilePaddingTopPx = Math.round((1 - largeFactor) * padding + largeFactor * largePadding);
108 requestLayout();
109 }
110
111 @Override
112 protected void onConfigurationChanged(Configuration newConfig) {
113 super.onConfigurationChanged(newConfig);
114 updateTopPadding();
115 FontSizeUtils.updateFontSize(mLabel, R.dimen.qs_tile_text_size);
116 if (mDualLabel != null) {
117 mDualLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
118 getResources().getDimensionPixelSize(R.dimen.qs_tile_text_size));
119 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400120 }
121
John Spurlockd47a3f32014-05-18 19:14:14 -0400122 private void recreateLabel() {
123 CharSequence labelText = null;
Selim Cineke5557a92014-08-15 19:59:23 +0200124 CharSequence labelDescription = null;
John Spurlockd47a3f32014-05-18 19:14:14 -0400125 if (mLabel != null) {
126 labelText = mLabel.getText();
127 removeView(mLabel);
John Spurlock92d9b192014-06-29 12:54:24 -0400128 mLabel = null;
129 }
130 if (mDualLabel != null) {
131 labelText = mDualLabel.getText();
Selim Cineke5557a92014-08-15 19:59:23 +0200132 labelDescription = mLabel.getContentDescription();
John Spurlock92d9b192014-06-29 12:54:24 -0400133 removeView(mDualLabel);
134 mDualLabel = null;
John Spurlockd47a3f32014-05-18 19:14:14 -0400135 }
136 final Resources res = mContext.getResources();
John Spurlock92d9b192014-06-29 12:54:24 -0400137 if (mDual) {
John Spurlock39076ed2014-06-30 20:47:20 -0400138 mDualLabel = new QSDualTileLabel(mContext);
John Spurlock92d9b192014-06-29 12:54:24 -0400139 mDualLabel.setId(android.R.id.title);
John Spurlock39076ed2014-06-30 20:47:20 -0400140 mDualLabel.setBackgroundResource(R.drawable.btn_borderless_rect);
Jason Monk5d325072014-10-27 11:38:47 -0400141 mDualLabel.setFirstLineCaret(res.getDrawable(R.drawable.qs_dual_tile_caret));
John Spurlock92d9b192014-06-29 12:54:24 -0400142 mDualLabel.setTextColor(res.getColor(R.color.qs_tile_text));
John Spurlock39076ed2014-06-30 20:47:20 -0400143 mDualLabel.setPadding(0, mDualTileVerticalPaddingPx, 0, mDualTileVerticalPaddingPx);
John Spurlock92d9b192014-06-29 12:54:24 -0400144 mDualLabel.setTypeface(CONDENSED);
145 mDualLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
146 res.getDimensionPixelSize(R.dimen.qs_tile_text_size));
147 mDualLabel.setClickable(true);
148 mDualLabel.setOnClickListener(mClickSecondary);
Selim Cineke32010a2014-08-20 23:50:41 +0200149 mDualLabel.setFocusable(true);
John Spurlock92d9b192014-06-29 12:54:24 -0400150 if (labelText != null) {
151 mDualLabel.setText(labelText);
152 }
Selim Cineke5557a92014-08-15 19:59:23 +0200153 if (labelDescription != null) {
154 mDualLabel.setContentDescription(labelDescription);
155 }
John Spurlock92d9b192014-06-29 12:54:24 -0400156 addView(mDualLabel);
157 } else {
158 mLabel = new TextView(mContext);
159 mLabel.setId(android.R.id.title);
160 mLabel.setTextColor(res.getColor(R.color.qs_tile_text));
161 mLabel.setGravity(Gravity.CENTER_HORIZONTAL);
162 mLabel.setMinLines(2);
163 mLabel.setPadding(0, 0, 0, 0);
164 mLabel.setTypeface(CONDENSED);
165 mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
166 res.getDimensionPixelSize(R.dimen.qs_tile_text_size));
167 mLabel.setClickable(false);
168 if (labelText != null) {
169 mLabel.setText(labelText);
170 }
171 addView(mLabel);
John Spurlockd47a3f32014-05-18 19:14:14 -0400172 }
John Spurlockd47a3f32014-05-18 19:14:14 -0400173 }
174
John Spurlockaf8d6c42014-05-07 17:49:08 -0400175 public void setDual(boolean dual) {
John Spurlockd47a3f32014-05-18 19:14:14 -0400176 final boolean changed = dual != mDual;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400177 mDual = dual;
John Spurlockd47a3f32014-05-18 19:14:14 -0400178 if (changed) {
179 recreateLabel();
180 }
John Spurlock31798f12014-11-04 09:41:32 -0500181 if (mTileBackground instanceof RippleDrawable) {
182 setRipple((RippleDrawable) mTileBackground);
Selim Cineke32010a2014-08-20 23:50:41 +0200183 }
184 if (dual) {
185 mTopBackgroundView.setOnClickListener(mClickPrimary);
186 setOnClickListener(null);
187 setClickable(false);
188 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
John Spurlock31798f12014-11-04 09:41:32 -0500189 mTopBackgroundView.setBackground(mTileBackground);
Selim Cineke32010a2014-08-20 23:50:41 +0200190 } else {
191 mTopBackgroundView.setOnClickListener(null);
192 mTopBackgroundView.setClickable(false);
193 setOnClickListener(mClickPrimary);
John Spurlockc247b8f2014-11-06 23:06:25 -0500194 setOnLongClickListener(mLongClick);
Selim Cineke32010a2014-08-20 23:50:41 +0200195 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
John Spurlock31798f12014-11-04 09:41:32 -0500196 setBackground(mTileBackground);
Selim Cineke32010a2014-08-20 23:50:41 +0200197 }
198 mTopBackgroundView.setFocusable(dual);
199 setFocusable(!dual);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400200 mDivider.setVisibility(dual ? VISIBLE : GONE);
201 postInvalidate();
202 }
203
Selim Cineke32010a2014-08-20 23:50:41 +0200204 private void setRipple(RippleDrawable tileBackground) {
205 mRipple = tileBackground;
206 if (getWidth() != 0) {
207 updateRippleSize(getWidth(), getHeight());
208 }
209 }
210
John Spurlockc247b8f2014-11-06 23:06:25 -0500211 public void init(OnClickListener clickPrimary, OnClickListener clickSecondary,
212 OnLongClickListener longClick) {
John Spurlockaf8d6c42014-05-07 17:49:08 -0400213 mClickPrimary = clickPrimary;
214 mClickSecondary = clickSecondary;
John Spurlockc247b8f2014-11-06 23:06:25 -0500215 mLongClick = longClick;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400216 }
217
218 protected View createIcon() {
John Spurlock83e28482014-05-18 17:20:05 -0400219 final ImageView icon = new ImageView(mContext);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400220 icon.setId(android.R.id.icon);
221 icon.setScaleType(ScaleType.CENTER_INSIDE);
222 return icon;
223 }
224
John Spurlock31798f12014-11-04 09:41:32 -0500225 private Drawable newTileBackground() {
John Spurlock856edeb2014-06-01 20:36:47 -0400226 final int[] attrs = new int[] { android.R.attr.selectableItemBackgroundBorderless };
John Spurlockaf8d6c42014-05-07 17:49:08 -0400227 final TypedArray ta = mContext.obtainStyledAttributes(attrs);
228 final Drawable d = ta.getDrawable(0);
229 ta.recycle();
230 return d;
231 }
232
John Spurlock92d9b192014-06-29 12:54:24 -0400233 private View labelView() {
234 return mDual ? mDualLabel : mLabel;
235 }
236
John Spurlockaf8d6c42014-05-07 17:49:08 -0400237 @Override
238 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
239 final int w = MeasureSpec.getSize(widthMeasureSpec);
240 final int h = MeasureSpec.getSize(heightMeasureSpec);
John Spurlock4bf31982014-05-21 13:04:22 -0400241 final int iconSpec = exactly(mIconSizePx);
Jorim Jaggic737b9b2014-09-08 23:57:20 +0200242 mIcon.measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.AT_MOST), iconSpec);
John Spurlock92d9b192014-06-29 12:54:24 -0400243 labelView().measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(h, MeasureSpec.AT_MOST));
John Spurlockaf8d6c42014-05-07 17:49:08 -0400244 if (mDual) {
245 mDivider.measure(widthMeasureSpec, exactly(mDivider.getLayoutParams().height));
246 }
Selim Cineke32010a2014-08-20 23:50:41 +0200247 int heightSpec = exactly(
248 mIconSizePx + mTilePaddingBelowIconPx + mTilePaddingTopPx);
249 mTopBackgroundView.measure(widthMeasureSpec, heightSpec);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400250 setMeasuredDimension(w, h);
251 }
252
253 private static int exactly(int size) {
254 return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
255 }
256
257 @Override
258 protected void onLayout(boolean changed, int l, int t, int r, int b) {
259 final int w = getMeasuredWidth();
John Spurlock529aa732014-07-10 22:49:16 -0400260 final int h = getMeasuredHeight();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400261
Selim Cineke32010a2014-08-20 23:50:41 +0200262 layout(mTopBackgroundView, 0, mTileSpacingPx);
263
John Spurlock92d9b192014-06-29 12:54:24 -0400264 int top = 0;
265 top += mTileSpacingPx;
266 top += mTilePaddingTopPx;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400267 final int iconLeft = (w - mIcon.getMeasuredWidth()) / 2;
268 layout(mIcon, iconLeft, top);
John Spurlock4bf31982014-05-21 13:04:22 -0400269 if (mRipple != null) {
Selim Cineke32010a2014-08-20 23:50:41 +0200270 updateRippleSize(w, h);
271
John Spurlock4bf31982014-05-21 13:04:22 -0400272 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400273 top = mIcon.getBottom();
John Spurlock92d9b192014-06-29 12:54:24 -0400274 top += mTilePaddingBelowIconPx;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400275 if (mDual) {
John Spurlockaf8d6c42014-05-07 17:49:08 -0400276 layout(mDivider, 0, top);
277 top = mDivider.getBottom();
278 }
John Spurlock92d9b192014-06-29 12:54:24 -0400279 layout(labelView(), 0, top);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400280 }
281
Selim Cineke32010a2014-08-20 23:50:41 +0200282 private void updateRippleSize(int width, int height) {
283 // center the touch feedback on the center of the icon, and dial it down a bit
284 final int cx = width / 2;
285 final int cy = mDual ? mIcon.getTop() + mIcon.getHeight() / 2 : height / 2;
286 final int rad = (int)(mIcon.getHeight() * 1.25f);
287 mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad);
288 }
289
John Spurlockaf8d6c42014-05-07 17:49:08 -0400290 private static void layout(View child, int left, int top) {
291 child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
292 }
293
294 protected void handleStateChanged(QSTile.State state) {
John Spurlock83e28482014-05-18 17:20:05 -0400295 if (mIcon instanceof ImageView) {
John Spurlock2d695812014-10-30 13:25:21 -0400296 setIcon((ImageView) mIcon, state);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400297 }
John Spurlock92d9b192014-06-29 12:54:24 -0400298 if (mDual) {
299 mDualLabel.setText(state.label);
Selim Cineke5557a92014-08-15 19:59:23 +0200300 mDualLabel.setContentDescription(state.dualLabelContentDescription);
Jason Monkec81d382014-09-18 16:11:09 -0400301 mTopBackgroundView.setContentDescription(state.contentDescription);
John Spurlock92d9b192014-06-29 12:54:24 -0400302 } else {
303 mLabel.setText(state.label);
Jason Monkec81d382014-09-18 16:11:09 -0400304 setContentDescription(state.contentDescription);
John Spurlock92d9b192014-06-29 12:54:24 -0400305 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400306 }
307
John Spurlock2d695812014-10-30 13:25:21 -0400308 protected void setIcon(ImageView iv, QSTile.State state) {
309 if (!Objects.equals(state.icon, iv.getTag(R.id.qs_icon_tag))) {
310 Drawable d = state.icon != null ? state.icon.getDrawable(mContext) : null;
311 if (d != null && state.autoMirrorDrawable) {
312 d.setAutoMirrored(true);
313 }
314 iv.setImageDrawable(d);
315 iv.setTag(R.id.qs_icon_tag, state.icon);
316 if (d instanceof Animatable) {
317 if (!iv.isShown()) {
318 ((Animatable) d).stop(); // skip directly to end state
319 }
320 }
321 }
322 }
323
John Spurlockaf8d6c42014-05-07 17:49:08 -0400324 public void onStateChanged(QSTile.State state) {
325 mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget();
326 }
327
328 private class H extends Handler {
329 private static final int STATE_CHANGED = 1;
330 public H() {
331 super(Looper.getMainLooper());
332 }
333 @Override
334 public void handleMessage(Message msg) {
335 if (msg.what == STATE_CHANGED) {
336 handleStateChanged((State) msg.obj);
337 }
338 }
339 }
Selim Cinekece4a832014-08-22 15:35:53 +0000340}