blob: af9d3a5919a4d526a5b5b3ab9d8fc54a50c7711d [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.qs;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.MathUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.TextView;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile.AnimationIcon;
import com.android.systemui.qs.QSTile.State;
import java.util.Objects;
/** View that represents a standard quick settings tile. **/
public class QSTileView extends ViewGroup {
private static final Typeface CONDENSED = Typeface.create("sans-serif-condensed",
Typeface.NORMAL);
protected final Context mContext;
private final View mIcon;
private final View mDivider;
private final H mHandler = new H();
private final int mIconSizePx;
private final int mTileSpacingPx;
private int mTilePaddingTopPx;
private final int mTilePaddingBelowIconPx;
private final int mDualTileVerticalPaddingPx;
private final View mTopBackgroundView;
private TextView mLabel;
private QSDualTileLabel mDualLabel;
private boolean mDual;
private OnClickListener mClickPrimary;
private OnClickListener mClickSecondary;
private OnLongClickListener mLongClick;
private Drawable mTileBackground;
private RippleDrawable mRipple;
public QSTileView(Context context) {
super(context);
mContext = context;
final Resources res = context.getResources();
mIconSizePx = res.getDimensionPixelSize(R.dimen.qs_tile_icon_size);
mTileSpacingPx = res.getDimensionPixelSize(R.dimen.qs_tile_spacing);
mTilePaddingBelowIconPx = res.getDimensionPixelSize(R.dimen.qs_tile_padding_below_icon);
mDualTileVerticalPaddingPx =
res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_vertical);
mTileBackground = newTileBackground();
recreateLabel();
setClipChildren(false);
mTopBackgroundView = new View(context);
addView(mTopBackgroundView);
mIcon = createIcon();
addView(mIcon);
mDivider = new View(mContext);
mDivider.setBackgroundColor(context.getColor(R.color.qs_tile_divider));
final int dh = res.getDimensionPixelSize(R.dimen.qs_tile_divider_height);
mDivider.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, dh));
addView(mDivider);
setClickable(true);
updateTopPadding();
}
private void updateTopPadding() {
Resources res = getResources();
int padding = res.getDimensionPixelSize(R.dimen.qs_tile_padding_top);
int largePadding = res.getDimensionPixelSize(R.dimen.qs_tile_padding_top_large_text);
float largeFactor = (MathUtils.constrain(getResources().getConfiguration().fontScale,
1.0f, FontSizeUtils.LARGE_TEXT_SCALE) - 1f) / (FontSizeUtils.LARGE_TEXT_SCALE - 1f);
mTilePaddingTopPx = Math.round((1 - largeFactor) * padding + largeFactor * largePadding);
requestLayout();
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateTopPadding();
FontSizeUtils.updateFontSize(mLabel, R.dimen.qs_tile_text_size);
if (mDualLabel != null) {
mDualLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
getResources().getDimensionPixelSize(R.dimen.qs_tile_text_size));
}
}
private void recreateLabel() {
CharSequence labelText = null;
CharSequence labelDescription = null;
if (mLabel != null) {
labelText = mLabel.getText();
removeView(mLabel);
mLabel = null;
}
if (mDualLabel != null) {
labelText = mDualLabel.getText();
labelDescription = mLabel.getContentDescription();
removeView(mDualLabel);
mDualLabel = null;
}
final Resources res = mContext.getResources();
if (mDual) {
mDualLabel = new QSDualTileLabel(mContext);
mDualLabel.setId(android.R.id.title);
mDualLabel.setBackgroundResource(R.drawable.btn_borderless_rect);
mDualLabel.setFirstLineCaret(mContext.getDrawable(R.drawable.qs_dual_tile_caret));
mDualLabel.setTextColor(mContext.getColor(R.color.qs_tile_text));
mDualLabel.setPadding(0, mDualTileVerticalPaddingPx, 0, mDualTileVerticalPaddingPx);
mDualLabel.setTypeface(CONDENSED);
mDualLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
res.getDimensionPixelSize(R.dimen.qs_tile_text_size));
mDualLabel.setClickable(true);
mDualLabel.setOnClickListener(mClickSecondary);
mDualLabel.setFocusable(true);
if (labelText != null) {
mDualLabel.setText(labelText);
}
if (labelDescription != null) {
mDualLabel.setContentDescription(labelDescription);
}
addView(mDualLabel);
} else {
mLabel = new TextView(mContext);
mLabel.setId(android.R.id.title);
mLabel.setTextColor(mContext.getColor(R.color.qs_tile_text));
mLabel.setGravity(Gravity.CENTER_HORIZONTAL);
mLabel.setMinLines(2);
mLabel.setPadding(0, 0, 0, 0);
mLabel.setTypeface(CONDENSED);
mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
res.getDimensionPixelSize(R.dimen.qs_tile_text_size));
mLabel.setClickable(false);
if (labelText != null) {
mLabel.setText(labelText);
}
addView(mLabel);
}
}
public boolean setDual(boolean dual) {
final boolean changed = dual != mDual;
mDual = dual;
if (changed) {
recreateLabel();
}
if (mTileBackground instanceof RippleDrawable) {
setRipple((RippleDrawable) mTileBackground);
}
if (dual) {
mTopBackgroundView.setOnClickListener(mClickPrimary);
setOnClickListener(null);
setClickable(false);
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
mTopBackgroundView.setBackground(mTileBackground);
} else {
mTopBackgroundView.setOnClickListener(null);
mTopBackgroundView.setClickable(false);
setOnClickListener(mClickPrimary);
setOnLongClickListener(mLongClick);
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
setBackground(mTileBackground);
}
mTopBackgroundView.setFocusable(dual);
setFocusable(!dual);
mDivider.setVisibility(dual ? VISIBLE : GONE);
postInvalidate();
return changed;
}
private void setRipple(RippleDrawable tileBackground) {
mRipple = tileBackground;
if (getWidth() != 0) {
updateRippleSize(getWidth(), getHeight());
}
}
public void init(OnClickListener clickPrimary, OnClickListener clickSecondary,
OnLongClickListener longClick) {
mClickPrimary = clickPrimary;
mClickSecondary = clickSecondary;
mLongClick = longClick;
}
protected View createIcon() {
final ImageView icon = new ImageView(mContext);
icon.setId(android.R.id.icon);
icon.setScaleType(ScaleType.CENTER_INSIDE);
return icon;
}
private Drawable newTileBackground() {
final int[] attrs = new int[] { android.R.attr.selectableItemBackgroundBorderless };
final TypedArray ta = mContext.obtainStyledAttributes(attrs);
final Drawable d = ta.getDrawable(0);
ta.recycle();
return d;
}
private View labelView() {
return mDual ? mDualLabel : mLabel;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int w = MeasureSpec.getSize(widthMeasureSpec);
final int h = MeasureSpec.getSize(heightMeasureSpec);
final int iconSpec = exactly(mIconSizePx);
mIcon.measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.AT_MOST), iconSpec);
labelView().measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(h, MeasureSpec.AT_MOST));
if (mDual) {
mDivider.measure(widthMeasureSpec, exactly(mDivider.getLayoutParams().height));
}
int heightSpec = exactly(
mIconSizePx + mTilePaddingBelowIconPx + mTilePaddingTopPx);
mTopBackgroundView.measure(widthMeasureSpec, heightSpec);
setMeasuredDimension(w, h);
}
private static int exactly(int size) {
return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int w = getMeasuredWidth();
final int h = getMeasuredHeight();
layout(mTopBackgroundView, 0, mTileSpacingPx);
int top = 0;
top += mTileSpacingPx;
top += mTilePaddingTopPx;
final int iconLeft = (w - mIcon.getMeasuredWidth()) / 2;
layout(mIcon, iconLeft, top);
if (mRipple != null) {
updateRippleSize(w, h);
}
top = mIcon.getBottom();
top += mTilePaddingBelowIconPx;
if (mDual) {
layout(mDivider, 0, top);
top = mDivider.getBottom();
}
layout(labelView(), 0, top);
}
private void updateRippleSize(int width, int height) {
// center the touch feedback on the center of the icon, and dial it down a bit
final int cx = width / 2;
final int cy = mDual ? mIcon.getTop() + mIcon.getHeight() / 2 : height / 2;
final int rad = (int)(mIcon.getHeight() * 1.25f);
mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad);
}
private static void layout(View child, int left, int top) {
child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
}
protected void handleStateChanged(QSTile.State state) {
if (mIcon instanceof ImageView) {
setIcon((ImageView) mIcon, state);
}
if (mDual) {
mDualLabel.setText(state.label);
mDualLabel.setContentDescription(state.dualLabelContentDescription);
mTopBackgroundView.setContentDescription(state.contentDescription);
} else {
mLabel.setText(state.label);
setContentDescription(state.contentDescription);
}
}
protected void setIcon(ImageView iv, QSTile.State state) {
if (!Objects.equals(state.icon, iv.getTag(R.id.qs_icon_tag))) {
Drawable d = state.icon != null ? state.icon.getDrawable(mContext) : null;
if (d != null && state.autoMirrorDrawable) {
d.setAutoMirrored(true);
}
iv.setImageDrawable(d);
iv.setTag(R.id.qs_icon_tag, state.icon);
if (d instanceof Animatable) {
Animatable a = (Animatable) d;
if (state.icon instanceof AnimationIcon && !iv.isShown()) {
a.stop(); // skip directly to end state
}
}
}
}
public void onStateChanged(QSTile.State state) {
mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget();
}
private class H extends Handler {
private static final int STATE_CHANGED = 1;
public H() {
super(Looper.getMainLooper());
}
@Override
public void handleMessage(Message msg) {
if (msg.what == STATE_CHANGED) {
handleStateChanged((State) msg.obj);
}
}
}
}