diff options
6 files changed, 249 insertions, 42 deletions
diff --git a/packages/SystemUI/res/drawable/qs_dual_tile_caret.xml b/packages/SystemUI/res/drawable/qs_dual_tile_caret.xml new file mode 100644 index 000000000000..a3c067bcd2e0 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_dual_tile_caret.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<nine-patch xmlns:android="http://schemas.android.com/apk/res/android" + android:src="@drawable/spinner_mtrl_am_alpha" + android:tint="@color/qs_tile_text" + android:autoMirrored="true" /> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 370ff1c8f1a5..f596edce5d03 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -159,14 +159,18 @@ <!-- For phones, this is close_handle_height + header_height --> <dimen name="peek_height">84dp</dimen> - <dimen name="qs_tile_height">84dp</dimen> - <dimen name="qs_tile_padding">8dp</dimen> + <dimen name="qs_tile_height">88dp</dimen> <dimen name="qs_tile_icon_size">28dp</dimen> <dimen name="qs_tile_text_size">12sp</dimen> <dimen name="qs_tile_divider_height">1dp</dimen> <dimen name="qs_panel_padding">16dp</dimen> - <dimen name="qs_dual_tile_height">109dp</dimen> - <dimen name="qs_dual_tile_padding">12dp</dimen> + <dimen name="qs_dual_tile_height">104dp</dimen> + <dimen name="qs_dual_tile_padding_below_divider">4dp</dimen> + <dimen name="qs_tile_padding_top">16dp</dimen> + <dimen name="qs_tile_padding_below_icon">12dp</dimen> + <dimen name="qs_tile_padding_bottom">16dp</dimen> + <dimen name="qs_tile_spacing">4dp</dimen> + <dimen name="qs_panel_padding_bottom">8dp</dimen> <!-- How far the expanded QS panel peeks from the header in collapsed state. --> <dimen name="qs_peek_height">8dp</dimen> diff --git a/packages/SystemUI/res/values/internal.xml b/packages/SystemUI/res/values/internal.xml index 7b93d318f156..5a19efb2bda6 100644 --- a/packages/SystemUI/res/values/internal.xml +++ b/packages/SystemUI/res/values/internal.xml @@ -18,5 +18,6 @@ <dimen name="status_bar_height">@*android:dimen/status_bar_height</dimen> <dimen name="navigation_bar_height">@*android:dimen/navigation_bar_height</dimen> <drawable name="notification_material_bg">@*android:drawable/notification_material_bg</drawable> + <drawable name="spinner_mtrl_am_alpha">@*android:drawable/spinner_mtrl_am_alpha</drawable> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDualTileLabel.java b/packages/SystemUI/src/com/android/systemui/qs/QSDualTileLabel.java new file mode 100644 index 000000000000..652676bab5e9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDualTileLabel.java @@ -0,0 +1,154 @@ +/* + * 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.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.text.TextUtils.TruncateAt; +import android.view.Gravity; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.TextView; + +import java.util.Objects; + +/** + * Text displayed over one or two lines, centered horizontally. A caret is always drawn at the end + * of the first line, and considered part of the content for centering purposes. + * + * Text overflow rules: + * First line: break on a word, unless a single word takes up the entire line - in which case + * truncate. + * Second line: ellipsis if necessary + */ +public class QSDualTileLabel extends FrameLayout { + + private static final String SPACING_TEXT = " "; + + private final Context mContext; + private final TextView mFirstLine; + private final TextView mSecondLine; + + private String mText; + + public QSDualTileLabel(Context context) { + super(context); + mContext = context; + mFirstLine = initTextView(); + mSecondLine = initTextView(); + mSecondLine.setEllipsize(TruncateAt.END); + addOnLayoutChangeListener(new OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, + int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + if ((oldRight - oldLeft) != (right - left)) { + updateText(); + } + } + }); + } + + public void setFirstLineBackground(Drawable d) { + mFirstLine.setBackground(d); + if (d != null) { + final LayoutParams lp = (LayoutParams) mSecondLine.getLayoutParams(); + lp.topMargin = d.getIntrinsicHeight() * 3 / 4; + mSecondLine.setLayoutParams(lp); + } + } + + private TextView initTextView() { + final TextView tv = new TextView(mContext); + tv.setPadding(0, 0, 0, 0); + tv.setSingleLine(true); + tv.setClickable(false); + tv.setBackground(null); + final LayoutParams lp = + new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + lp.gravity = Gravity.CENTER_HORIZONTAL; + addView(tv, lp); + return tv; + } + + public void setText(CharSequence text) { + final String newText = text == null ? null : text.toString().trim(); + if (Objects.equals(newText, mText)) return; + mText = newText; + updateText(); + } + + public String getText() { + return mText; + } + + public void setTextSize(int unit, float size) { + mFirstLine.setTextSize(unit, size); + mSecondLine.setTextSize(unit, size); + } + + public void setTextColor(int color) { + mFirstLine.setTextColor(color); + mSecondLine.setTextColor(color); + } + + public void setTypeface(Typeface tf) { + mFirstLine.setTypeface(tf); + mSecondLine.setTypeface(tf); + } + + private void updateText() { + if (getWidth() == 0) return; + if (TextUtils.isEmpty(mText)) { + mFirstLine.setText(null); + mSecondLine.setText(null); + return; + } + final float maxWidth = getWidth() - mFirstLine.getBackground().getIntrinsicWidth() + - getPaddingLeft() - getPaddingRight(); + float width = mFirstLine.getPaint().measureText(mText + SPACING_TEXT); + if (width <= maxWidth) { + mFirstLine.setText(mText + SPACING_TEXT); + mSecondLine.setText(null); + return; + } + final int n = mText.length(); + int lastWordBoundary = -1; + boolean inWhitespace = false; + int i = 0; + for (i = 1; i < n; i++) { + if (Character.isWhitespace(mText.charAt(i))) { + if (!inWhitespace) { + lastWordBoundary = i; + } + inWhitespace = true; + } else { + inWhitespace = false; + } + width = mFirstLine.getPaint().measureText(mText.substring(0, i) + SPACING_TEXT); + if (width > maxWidth) { + break; + } + } + if (lastWordBoundary == -1) { + lastWordBoundary = i - 1; + } + mFirstLine.setText(mText.substring(0, lastWordBoundary) + SPACING_TEXT); + mSecondLine.setText(mText.substring(lastWordBoundary).trim()); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 07ffd6613173..a044bb1c0a62 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -47,6 +47,7 @@ public class QSPanel extends ViewGroup { private int mCellHeight; private int mLargeCellWidth; private int mLargeCellHeight; + private int mPanelPaddingBottom; private boolean mExpanded; private TileRecord mDetailRecord; @@ -80,6 +81,7 @@ public class QSPanel extends ViewGroup { mCellWidth = (int)(mCellHeight * TILE_ASPECT); mLargeCellHeight = res.getDimensionPixelSize(R.dimen.qs_dual_tile_height); mLargeCellWidth = (int)(mLargeCellHeight * TILE_ASPECT); + mPanelPaddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom); if (mColumns != columns) { mColumns = columns; postInvalidate(); @@ -204,7 +206,7 @@ public class QSPanel extends ViewGroup { final int ch = record.row == 0 ? mLargeCellHeight : mCellHeight; record.tileView.measure(exactly(cw), exactly(ch)); } - int h = rows == 0 ? 0 : getRowTop(rows); + int h = rows == 0 ? 0 : (getRowTop(rows) + mPanelPaddingBottom); mDetail.measure(exactly(width), unspecified()); if (mDetail.getVisibility() == VISIBLE && mDetail.getChildCount() > 0) { final int dmh = mDetail.getMeasuredHeight(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java index 5388994af368..062e36cf3319 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java @@ -47,9 +47,13 @@ public class QSTileView extends ViewGroup { private final View mDivider; private final H mHandler = new H(); private final int mIconSizePx; + private final int mTileSpacingPx; + private final int mTilePaddingTopPx; + private final int mTilePaddingBelowIconPx; + private final int mDualTilePaddingBelowDividerPx; - private int mTilePaddingPx; private TextView mLabel; + private QSDualTileLabel mDualLabel; private boolean mDual; private OnClickListener mClickPrimary; private OnClickListener mClickSecondary; @@ -61,6 +65,11 @@ public class QSTileView extends ViewGroup { mContext = context; final Resources res = context.getResources(); mIconSizePx = res.getDimensionPixelSize(R.dimen.qs_tile_icon_size); + mTileSpacingPx = res.getDimensionPixelSize(R.dimen.qs_tile_spacing); + mTilePaddingTopPx = res.getDimensionPixelSize(R.dimen.qs_tile_padding_top); + mTilePaddingBelowIconPx = res.getDimensionPixelSize(R.dimen.qs_tile_padding_below_icon); + mDualTilePaddingBelowDividerPx = + res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_below_divider); recreateLabel(); setClipChildren(false); @@ -82,26 +91,46 @@ public class QSTileView extends ViewGroup { if (mLabel != null) { labelText = mLabel.getText(); removeView(mLabel); + mLabel = null; + } + if (mDualLabel != null) { + labelText = mDualLabel.getText(); + removeView(mDualLabel); + mDualLabel = null; } final Resources res = mContext.getResources(); - mLabel = new TextView(mDual - ? new ContextThemeWrapper(mContext, R.style.BorderlessButton_Tiny) - : mContext); - mLabel.setId(android.R.id.title); - mLabel.setTextColor(res.getColor(R.color.qs_tile_text)); - mLabel.setGravity(Gravity.CENTER_HORIZONTAL); - mLabel.setMinLines(2); - mTilePaddingPx = res.getDimensionPixelSize( - mDual ? R.dimen.qs_dual_tile_padding : R.dimen.qs_tile_padding); - final int bottomPadding = mDual ? 0 : mTilePaddingPx; - mLabel.setPadding(mTilePaddingPx, mTilePaddingPx, mTilePaddingPx, bottomPadding); - mLabel.setTypeface(CONDENSED); - mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, - res.getDimensionPixelSize(R.dimen.qs_tile_text_size)); - if (labelText != null) { - mLabel.setText(labelText); + if (mDual) { + final Context c = new ContextThemeWrapper(mContext, R.style.BorderlessButton_Tiny); + mDualLabel = new QSDualTileLabel(c); + mDualLabel.setId(android.R.id.title); + mDualLabel.setFirstLineBackground(res.getDrawable(R.drawable.qs_dual_tile_caret)); + mDualLabel.setTextColor(res.getColor(R.color.qs_tile_text)); + mDualLabel.setPadding(0, mDualTilePaddingBelowDividerPx, 0, 0); + mDualLabel.setTypeface(CONDENSED); + mDualLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, + res.getDimensionPixelSize(R.dimen.qs_tile_text_size)); + mDualLabel.setClickable(true); + mDualLabel.setOnClickListener(mClickSecondary); + if (labelText != null) { + mDualLabel.setText(labelText); + } + addView(mDualLabel); + } else { + mLabel = new TextView(mContext); + mLabel.setId(android.R.id.title); + mLabel.setTextColor(res.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); } - addView(mLabel); } public void setDual(boolean dual) { @@ -110,14 +139,7 @@ public class QSTileView extends ViewGroup { if (changed) { recreateLabel(); } - if (mDual) { - setOnClickListener(mClickPrimary); - mLabel.setClickable(true); - mLabel.setOnClickListener(mClickSecondary); - } else { - mLabel.setClickable(false); - setOnClickListener(mClickPrimary); - } + setOnClickListener(mClickPrimary); mDivider.setVisibility(dual ? VISIBLE : GONE); postInvalidate(); } @@ -145,13 +167,17 @@ public class QSTileView extends ViewGroup { 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(iconSpec, iconSpec); - mLabel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(h, MeasureSpec.AT_MOST)); + labelView().measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(h, MeasureSpec.AT_MOST)); if (mDual) { mDivider.measure(widthMeasureSpec, exactly(mDivider.getLayoutParams().height)); } @@ -165,14 +191,10 @@ public class QSTileView extends ViewGroup { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int w = getMeasuredWidth(); - final int h = getMeasuredHeight(); - final int contentHeight = mTilePaddingPx + mIcon.getMeasuredHeight() - + mLabel.getMeasuredHeight() - + (mDual ? (mTilePaddingPx + mDivider.getMeasuredHeight()) : 0); - - int top = Math.max(0, (h - contentHeight) / 2); - top += mTilePaddingPx; + int top = 0; + top += mTileSpacingPx; + top += mTilePaddingTopPx; final int iconLeft = (w - mIcon.getMeasuredWidth()) / 2; layout(mIcon, iconLeft, top); if (mRipple != null) { @@ -183,12 +205,12 @@ public class QSTileView extends ViewGroup { mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad); } top = mIcon.getBottom(); + top += mTilePaddingBelowIconPx; if (mDual) { - top += mTilePaddingPx; layout(mDivider, 0, top); top = mDivider.getBottom(); } - layout(mLabel, 0, top); + layout(labelView(), 0, top); } private static void layout(View child, int left, int top) { @@ -204,7 +226,11 @@ public class QSTileView extends ViewGroup { iv.setImageResource(state.iconId); } } - mLabel.setText(state.label); + if (mDual) { + mDualLabel.setText(state.label); + } else { + mLabel.setText(state.label); + } setContentDescription(state.contentDescription); } |