Home | History | Annotate | Download | only in qs
      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 
     17 package com.android.systemui.qs;
     18 
     19 import android.content.Context;
     20 import android.content.res.Configuration;
     21 import android.content.res.Resources;
     22 import android.content.res.TypedArray;
     23 import android.graphics.Typeface;
     24 import android.graphics.drawable.Drawable;
     25 import android.graphics.drawable.RippleDrawable;
     26 import android.os.Handler;
     27 import android.os.Looper;
     28 import android.os.Message;
     29 import android.util.MathUtils;
     30 import android.util.TypedValue;
     31 import android.view.Gravity;
     32 import android.view.View;
     33 import android.view.ViewGroup;
     34 import android.widget.ImageView;
     35 import android.widget.ImageView.ScaleType;
     36 import android.widget.TextView;
     37 
     38 import com.android.systemui.FontSizeUtils;
     39 import com.android.systemui.R;
     40 import com.android.systemui.qs.QSTile.State;
     41 
     42 /** View that represents a standard quick settings tile. **/
     43 public class QSTileView extends ViewGroup {
     44     private static final Typeface CONDENSED = Typeface.create("sans-serif-condensed",
     45             Typeface.NORMAL);
     46 
     47     protected final Context mContext;
     48     private final View mIcon;
     49     private final View mDivider;
     50     private final H mHandler = new H();
     51     private final int mIconSizePx;
     52     private final int mTileSpacingPx;
     53     private int mTilePaddingTopPx;
     54     private final int mTilePaddingBelowIconPx;
     55     private final int mDualTileVerticalPaddingPx;
     56     private final View mTopBackgroundView;
     57 
     58     private TextView mLabel;
     59     private QSDualTileLabel mDualLabel;
     60     private boolean mDual;
     61     private OnClickListener mClickPrimary;
     62     private OnClickListener mClickSecondary;
     63     private RippleDrawable mRipple;
     64 
     65     public QSTileView(Context context) {
     66         super(context);
     67 
     68         mContext = context;
     69         final Resources res = context.getResources();
     70         mIconSizePx = res.getDimensionPixelSize(R.dimen.qs_tile_icon_size);
     71         mTileSpacingPx = res.getDimensionPixelSize(R.dimen.qs_tile_spacing);
     72         mTilePaddingBelowIconPx =  res.getDimensionPixelSize(R.dimen.qs_tile_padding_below_icon);
     73         mDualTileVerticalPaddingPx =
     74                 res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_vertical);
     75         recreateLabel();
     76         setClipChildren(false);
     77 
     78         mTopBackgroundView = new View(context);
     79         addView(mTopBackgroundView);
     80 
     81         mIcon = createIcon();
     82         addView(mIcon);
     83 
     84         mDivider = new View(mContext);
     85         mDivider.setBackgroundColor(res.getColor(R.color.qs_tile_divider));
     86         final int dh = res.getDimensionPixelSize(R.dimen.qs_tile_divider_height);
     87         mDivider.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, dh));
     88         addView(mDivider);
     89 
     90         setClickable(true);
     91 
     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         }
    114     }
    115 
    116     private void recreateLabel() {
    117         CharSequence labelText = null;
    118         CharSequence labelDescription = null;
    119         if (mLabel != null) {
    120             labelText = mLabel.getText();
    121             removeView(mLabel);
    122             mLabel = null;
    123         }
    124         if (mDualLabel != null) {
    125             labelText = mDualLabel.getText();
    126             labelDescription = mLabel.getContentDescription();
    127             removeView(mDualLabel);
    128             mDualLabel = null;
    129         }
    130         final Resources res = mContext.getResources();
    131         if (mDual) {
    132             mDualLabel = new QSDualTileLabel(mContext);
    133             mDualLabel.setId(android.R.id.title);
    134             mDualLabel.setBackgroundResource(R.drawable.btn_borderless_rect);
    135             mDualLabel.setTextColor(res.getColor(R.color.qs_tile_text));
    136             mDualLabel.setPadding(0, mDualTileVerticalPaddingPx, 0, mDualTileVerticalPaddingPx);
    137             mDualLabel.setTypeface(CONDENSED);
    138             mDualLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
    139                     res.getDimensionPixelSize(R.dimen.qs_tile_text_size));
    140             mDualLabel.setClickable(true);
    141             mDualLabel.setOnClickListener(mClickSecondary);
    142             mDualLabel.setFocusable(true);
    143             if (labelText != null) {
    144                 mDualLabel.setText(labelText);
    145             }
    146             if (labelDescription != null) {
    147                 mDualLabel.setContentDescription(labelDescription);
    148             }
    149             addView(mDualLabel);
    150         } else {
    151             mLabel = new TextView(mContext);
    152             mLabel.setId(android.R.id.title);
    153             mLabel.setTextColor(res.getColor(R.color.qs_tile_text));
    154             mLabel.setGravity(Gravity.CENTER_HORIZONTAL);
    155             mLabel.setMinLines(2);
    156             mLabel.setPadding(0, 0, 0, 0);
    157             mLabel.setTypeface(CONDENSED);
    158             mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
    159                     res.getDimensionPixelSize(R.dimen.qs_tile_text_size));
    160             mLabel.setClickable(false);
    161             if (labelText != null) {
    162                 mLabel.setText(labelText);
    163             }
    164             addView(mLabel);
    165         }
    166     }
    167 
    168     public void setDual(boolean dual) {
    169         final boolean changed = dual != mDual;
    170         mDual = dual;
    171         if (changed) {
    172             recreateLabel();
    173         }
    174         Drawable tileBackground = getTileBackground();
    175         if (tileBackground instanceof RippleDrawable) {
    176             setRipple((RippleDrawable) tileBackground);
    177         }
    178         if (dual) {
    179             mTopBackgroundView.setOnClickListener(mClickPrimary);
    180             setOnClickListener(null);
    181             setClickable(false);
    182             setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
    183             mTopBackgroundView.setBackground(tileBackground);
    184         } else {
    185             mTopBackgroundView.setOnClickListener(null);
    186             mTopBackgroundView.setClickable(false);
    187             setOnClickListener(mClickPrimary);
    188             setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
    189             setBackground(tileBackground);
    190         }
    191         mTopBackgroundView.setFocusable(dual);
    192         setFocusable(!dual);
    193         mDivider.setVisibility(dual ? VISIBLE : GONE);
    194         postInvalidate();
    195     }
    196 
    197     private void setRipple(RippleDrawable tileBackground) {
    198         mRipple = tileBackground;
    199         if (getWidth() != 0) {
    200             updateRippleSize(getWidth(), getHeight());
    201         }
    202     }
    203 
    204     public void init(OnClickListener clickPrimary, OnClickListener clickSecondary) {
    205         mClickPrimary = clickPrimary;
    206         mClickSecondary = clickSecondary;
    207     }
    208 
    209     protected View createIcon() {
    210         final ImageView icon = new ImageView(mContext);
    211         icon.setId(android.R.id.icon);
    212         icon.setScaleType(ScaleType.CENTER_INSIDE);
    213         return icon;
    214     }
    215 
    216     private Drawable getTileBackground() {
    217         final int[] attrs = new int[] { android.R.attr.selectableItemBackgroundBorderless };
    218         final TypedArray ta = mContext.obtainStyledAttributes(attrs);
    219         final Drawable d = ta.getDrawable(0);
    220         ta.recycle();
    221         return d;
    222     }
    223 
    224     private View labelView() {
    225         return mDual ? mDualLabel : mLabel;
    226     }
    227 
    228     @Override
    229     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    230         final int w = MeasureSpec.getSize(widthMeasureSpec);
    231         final int h = MeasureSpec.getSize(heightMeasureSpec);
    232         final int iconSpec = exactly(mIconSizePx);
    233         mIcon.measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.AT_MOST), iconSpec);
    234         labelView().measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(h, MeasureSpec.AT_MOST));
    235         if (mDual) {
    236             mDivider.measure(widthMeasureSpec, exactly(mDivider.getLayoutParams().height));
    237         }
    238         int heightSpec = exactly(
    239                 mIconSizePx + mTilePaddingBelowIconPx + mTilePaddingTopPx);
    240         mTopBackgroundView.measure(widthMeasureSpec, heightSpec);
    241         setMeasuredDimension(w, h);
    242     }
    243 
    244     private static int exactly(int size) {
    245         return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
    246     }
    247 
    248     @Override
    249     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    250         final int w = getMeasuredWidth();
    251         final int h = getMeasuredHeight();
    252 
    253         layout(mTopBackgroundView, 0, mTileSpacingPx);
    254 
    255         int top = 0;
    256         top += mTileSpacingPx;
    257         top += mTilePaddingTopPx;
    258         final int iconLeft = (w - mIcon.getMeasuredWidth()) / 2;
    259         layout(mIcon, iconLeft, top);
    260         if (mRipple != null) {
    261             updateRippleSize(w, h);
    262 
    263         }
    264         top = mIcon.getBottom();
    265         top += mTilePaddingBelowIconPx;
    266         if (mDual) {
    267             layout(mDivider, 0, top);
    268             top = mDivider.getBottom();
    269         }
    270         layout(labelView(), 0, top);
    271     }
    272 
    273     private void updateRippleSize(int width, int height) {
    274         // center the touch feedback on the center of the icon, and dial it down a bit
    275         final int cx = width / 2;
    276         final int cy = mDual ? mIcon.getTop() + mIcon.getHeight() / 2 : height / 2;
    277         final int rad = (int)(mIcon.getHeight() * 1.25f);
    278         mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad);
    279     }
    280 
    281     private static void layout(View child, int left, int top) {
    282         child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
    283     }
    284 
    285     protected void handleStateChanged(QSTile.State state) {
    286         if (mIcon instanceof ImageView) {
    287             ImageView iv = (ImageView) mIcon;
    288             if (state.icon != null) {
    289                 iv.setImageDrawable(state.icon);
    290             } else if (state.iconId > 0) {
    291                 iv.setImageResource(state.iconId);
    292             }
    293             Drawable drawable = iv.getDrawable();
    294             if (state.autoMirrorDrawable && drawable != null) {
    295                 drawable.setAutoMirrored(true);
    296             }
    297         }
    298         if (mDual) {
    299             mDualLabel.setText(state.label);
    300             mDualLabel.setContentDescription(state.dualLabelContentDescription);
    301             mTopBackgroundView.setContentDescription(state.contentDescription);
    302         } else {
    303             mLabel.setText(state.label);
    304             setContentDescription(state.contentDescription);
    305         }
    306     }
    307 
    308     public void onStateChanged(QSTile.State state) {
    309         mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget();
    310     }
    311 
    312     private class H extends Handler {
    313         private static final int STATE_CHANGED = 1;
    314         public H() {
    315             super(Looper.getMainLooper());
    316         }
    317         @Override
    318         public void handleMessage(Message msg) {
    319             if (msg.what == STATE_CHANGED) {
    320                 handleStateChanged((State) msg.obj);
    321             }
    322         }
    323     }
    324 }
    325