From 96ab63640f186dd7f983b35f2c4e1a95436e5052 Mon Sep 17 00:00:00 2001 From: Jason Monk Date: Thu, 11 Feb 2016 11:33:09 -0500 Subject: [PATCH] Battery graph in QS Detail Add new usage graph view to SettingsLib that shows usage with same labels and whatnot. Use that graph in the battery detail panel to show more stuffs. Change-Id: I397b1314f65f668df566e93bdbc15420e1b3a280 --- .../res/layout/usage_bottom_label.xml | 20 ++ .../res/layout/usage_side_label.xml | 20 ++ .../SettingsLib/res/layout/usage_view.xml | 86 ++++++ packages/SettingsLib/res/values/attrs.xml | 7 + packages/SettingsLib/res/values/colors.xml | 2 + packages/SettingsLib/res/values/dimens.xml | 15 ++ packages/SettingsLib/res/values/strings.xml | 15 ++ .../com/android/settingslib/BatteryInfo.java | 149 ++++++++++- .../android/settingslib/graph/UsageGraph.java | 249 ++++++++++++++++++ .../android/settingslib/graph/UsageView.java | 99 +++++++ .../SystemUI/res/layout/battery_detail.xml | 111 +++++--- .../res/values-sw600dp-land/config.xml | 3 + .../res/values-sw600dp-land/dimens.xml | 3 + .../res/values-w550dp-land/config.xml | 4 + .../res/values-w550dp-land/dimens.xml | 3 + packages/SystemUI/res/values/config.xml | 3 + packages/SystemUI/res/values/dimens.xml | 3 + packages/SystemUI/res/values/strings.xml | 2 +- .../systemui/qs/tiles/BatteryTile.java | 37 ++- 19 files changed, 773 insertions(+), 58 deletions(-) create mode 100644 packages/SettingsLib/res/layout/usage_bottom_label.xml create mode 100644 packages/SettingsLib/res/layout/usage_side_label.xml create mode 100644 packages/SettingsLib/res/layout/usage_view.xml create mode 100644 packages/SettingsLib/src/com/android/settingslib/graph/UsageGraph.java create mode 100644 packages/SettingsLib/src/com/android/settingslib/graph/UsageView.java diff --git a/packages/SettingsLib/res/layout/usage_bottom_label.xml b/packages/SettingsLib/res/layout/usage_bottom_label.xml new file mode 100644 index 0000000000000..6c168806338e5 --- /dev/null +++ b/packages/SettingsLib/res/layout/usage_bottom_label.xml @@ -0,0 +1,20 @@ + + diff --git a/packages/SettingsLib/res/layout/usage_side_label.xml b/packages/SettingsLib/res/layout/usage_side_label.xml new file mode 100644 index 0000000000000..6c168806338e5 --- /dev/null +++ b/packages/SettingsLib/res/layout/usage_side_label.xml @@ -0,0 +1,20 @@ + + diff --git a/packages/SettingsLib/res/layout/usage_view.xml b/packages/SettingsLib/res/layout/usage_view.xml new file mode 100644 index 0000000000000..56716d3b90fb1 --- /dev/null +++ b/packages/SettingsLib/res/layout/usage_view.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/SettingsLib/res/values/attrs.xml b/packages/SettingsLib/res/values/attrs.xml index 3e1fc4a4e343b..7aa422012a224 100644 --- a/packages/SettingsLib/res/values/attrs.xml +++ b/packages/SettingsLib/res/values/attrs.xml @@ -24,4 +24,11 @@ + + + + + + + diff --git a/packages/SettingsLib/res/values/colors.xml b/packages/SettingsLib/res/values/colors.xml index c090468c3e8f2..796273dc36a97 100644 --- a/packages/SettingsLib/res/values/colors.xml +++ b/packages/SettingsLib/res/values/colors.xml @@ -16,4 +16,6 @@ #66000000 + + #455A64 diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml index 811751cd00a5c..84d3bff6c68ce 100644 --- a/packages/SettingsLib/res/values/dimens.xml +++ b/packages/SettingsLib/res/values/dimens.xml @@ -38,4 +38,19 @@ 8dip + + 122dp + 9dp + 24dp + 72dp + 16dp + + 1dp + + 3dp + 6dp + + .75dp + 7dp + diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 6d047d0e51c81..20e5ac9537c6c 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -732,6 +732,9 @@ Overridden by %1$s + + Approx. %2$s left + %1$s - approx. %2$s left @@ -775,4 +778,16 @@ Home + + 0% + 50% + 100% + + + + %1$s ago + + + %1$s left + diff --git a/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java b/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java index d81bdebda33c7..aeb56a8179a48 100644 --- a/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java +++ b/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java @@ -17,13 +17,17 @@ package com.android.settingslib; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.res.Resources; import android.os.AsyncTask; import android.os.BatteryManager; import android.os.BatteryStats; +import android.os.BatteryStats.HistoryItem; import android.os.Bundle; import android.os.SystemClock; import android.text.format.Formatter; +import android.util.SparseIntArray; import com.android.internal.os.BatteryStatsHelper; +import com.android.settingslib.graph.UsageView; public class BatteryInfo { @@ -31,11 +35,127 @@ public class BatteryInfo { public int mBatteryLevel; public boolean mDischarging = true; public long remainingTimeUs = 0; + public String batteryPercentString; + public String remainingLabel; + private BatteryStats mStats; + private boolean mCharging; public interface Callback { void onBatteryInfoLoaded(BatteryInfo info); } + public void bindHistory(UsageView view) { + long startWalltime = 0; + long endDateWalltime = 0; + long endWalltime = 0; + long historyStart = 0; + long historyEnd = 0; + byte lastLevel = -1; + long curWalltime = startWalltime; + long lastWallTime = 0; + long lastRealtime = 0; + int lastInteresting = 0; + int pos = 0; + boolean first = true; + if (mStats.startIteratingHistoryLocked()) { + final HistoryItem rec = new HistoryItem(); + while (mStats.getNextHistoryLocked(rec)) { + pos++; + if (first) { + first = false; + historyStart = rec.time; + } + if (rec.cmd == HistoryItem.CMD_CURRENT_TIME + || rec.cmd == HistoryItem.CMD_RESET) { + // If there is a ridiculously large jump in time, then we won't be + // able to create a good chart with that data, so just ignore the + // times we got before and pretend like our data extends back from + // the time we have now. + // Also, if we are getting a time change and we are less than 5 minutes + // since the start of the history real time, then also use this new + // time to compute the base time, since whatever time we had before is + // pretty much just noise. + if (rec.currentTime > (lastWallTime+(180*24*60*60*1000L)) + || rec.time < (historyStart+(5*60*1000L))) { + startWalltime = 0; + } + lastWallTime = rec.currentTime; + lastRealtime = rec.time; + if (startWalltime == 0) { + startWalltime = lastWallTime - (lastRealtime-historyStart); + } + } + if (rec.isDeltaData()) { + if (rec.batteryLevel != lastLevel || pos == 1) { + lastLevel = rec.batteryLevel; + } + lastInteresting = pos; + historyEnd = rec.time; + } + } + } + mStats.finishIteratingHistoryLocked(); + endDateWalltime = lastWallTime + historyEnd - lastRealtime; + endWalltime = endDateWalltime + (remainingTimeUs / 1000); + + int i = 0; + final int N = lastInteresting; + SparseIntArray points = new SparseIntArray(); + view.clearPaths(); + view.configureGraph((int) (endWalltime - startWalltime), 100, remainingTimeUs != 0, + mCharging); + if (endDateWalltime > startWalltime && mStats.startIteratingHistoryLocked()) { + final HistoryItem rec = new HistoryItem(); + while (mStats.getNextHistoryLocked(rec) && i < N) { + if (rec.isDeltaData()) { + curWalltime += rec.time - lastRealtime; + lastRealtime = rec.time; + long x = (curWalltime - startWalltime); + if (x < 0) { + x = 0; + } + points.put((int) x, rec.batteryLevel); + } else { + long lastWalltime = curWalltime; + if (rec.cmd == HistoryItem.CMD_CURRENT_TIME + || rec.cmd == HistoryItem.CMD_RESET) { + if (rec.currentTime >= startWalltime) { + curWalltime = rec.currentTime; + } else { + curWalltime = startWalltime + (rec.time - historyStart); + } + lastRealtime = rec.time; + } + + if (rec.cmd != HistoryItem.CMD_OVERFLOW + && (rec.cmd != HistoryItem.CMD_CURRENT_TIME + || Math.abs(lastWalltime-curWalltime) > (60*60*1000))) { + if (points.size() > 1) { + view.addPath(points); + } + points.clear(); + } + } + i++; + } + } + if (points.size() > 1) { + view.addPath(points); + } + long timePast = endDateWalltime - startWalltime; + final Context context = view.getContext(); + String timeString = context.getString(R.string.charge_length_format, + Formatter.formatShortElapsedTime(context, timePast)); + String remaining = ""; + if (remainingTimeUs != 0) { + remaining = context.getString(R.string.remaining_length_format, + Formatter.formatShortElapsedTime(context, remainingTimeUs)); + } + view.setBottomLabels(new CharSequence[] { timeString, remaining}); + + mStats.finishIteratingHistoryLocked(); + } + public static void getBatteryInfo(final Context context, final Callback callback) { new AsyncTask() { @Override @@ -60,23 +180,29 @@ public class BatteryInfo { public static BatteryInfo getBatteryInfo(Context context, Intent batteryBroadcast, BatteryStats stats, long elapsedRealtimeUs) { BatteryInfo info = new BatteryInfo(); + info.mStats = stats; info.mBatteryLevel = Utils.getBatteryLevel(batteryBroadcast); - String batteryPercentString = Utils.formatPercentage(info.mBatteryLevel); - if (batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) == 0) { + info.batteryPercentString = Utils.formatPercentage(info.mBatteryLevel); + info.mCharging = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0; + final Resources resources = context.getResources(); + if (!info.mCharging) { final long drainTime = stats.computeBatteryTimeRemaining(elapsedRealtimeUs); if (drainTime > 0) { info.remainingTimeUs = drainTime; String timeString = Formatter.formatShortElapsedTime(context, drainTime / 1000); - info.mChargeLabelString = context.getResources().getString( - R.string.power_discharging_duration, batteryPercentString, timeString); + info.remainingLabel = resources.getString(R.string.power_remaining_duration_only, + timeString); + info.mChargeLabelString = resources.getString(R.string.power_discharging_duration, + info.batteryPercentString, timeString); } else { - info.mChargeLabelString = batteryPercentString; + info.remainingLabel = null; + info.mChargeLabelString = info.batteryPercentString; } } else { final long chargeTime = stats.computeChargeTimeRemaining(elapsedRealtimeUs); final String statusLabel = Utils.getBatteryStatus( - context.getResources(), batteryBroadcast); + resources, batteryBroadcast); final int status = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN); if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) { @@ -95,11 +221,14 @@ public class BatteryInfo { } else { resId = R.string.power_charging_duration; } - info.mChargeLabelString = context.getResources().getString( - resId, batteryPercentString, timeString); + info.remainingLabel = resources.getString(R.string.power_remaining_duration_only, + timeString); + info.mChargeLabelString = resources.getString( + resId, info.batteryPercentString, timeString); } else { - info.mChargeLabelString = context.getResources().getString( - R.string.power_charging, batteryPercentString, statusLabel); + info.remainingLabel = statusLabel; + info.mChargeLabelString = resources.getString( + R.string.power_charging, info.batteryPercentString, statusLabel); } } return info; diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/UsageGraph.java b/packages/SettingsLib/src/com/android/settingslib/graph/UsageGraph.java new file mode 100644 index 0000000000000..530ec16d5ccab --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/graph/UsageGraph.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2016 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.settingslib.graph; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.CornerPathEffect; +import android.graphics.DashPathEffect; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.Paint.Cap; +import android.graphics.Paint.Join; +import android.graphics.Paint.Style; +import android.graphics.Path; +import android.graphics.Shader.TileMode; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.SparseIntArray; +import android.util.TypedValue; +import android.view.View; +import com.android.settingslib.R; + +public class UsageGraph extends View { + + private static final int PATH_DELIM = -1; + + private final Paint mLinePaint; + private final Paint mFillPaint; + private final Paint mDottedPaint; + + private final Drawable mDivider; + private final int mDividerSize; + + private final Path mPath = new Path(); + + // Paths in coordinates they are passed in. + private final SparseIntArray mPaths = new SparseIntArray(); + // Paths in local coordinates for drawing. + private final SparseIntArray mLocalPaths = new SparseIntArray(); + + private int mAccentColor; + private boolean mShowProjection; + private boolean mProjectUp; + + private float mMaxX = 100; + private float mMaxY = 100; + + public UsageGraph(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + final Resources resources = context.getResources(); + + mLinePaint = new Paint(); + mLinePaint.setStyle(Style.STROKE); + mLinePaint.setStrokeCap(Cap.ROUND); + mLinePaint.setStrokeJoin(Join.ROUND); + mLinePaint.setAntiAlias(true); + mLinePaint.setPathEffect(new CornerPathEffect(resources.getDimensionPixelSize( + R.dimen.usage_graph_line_corner_radius))); + mLinePaint.setStrokeWidth(resources.getDimensionPixelSize(R.dimen.usage_graph_line_width)); + + mFillPaint = new Paint(mLinePaint); + mFillPaint.setStyle(Style.FILL); + + mDottedPaint = new Paint(mLinePaint); + mDottedPaint.setStyle(Style.STROKE); + float dots = resources.getDimensionPixelSize(R.dimen.usage_graph_dot_size); + float interval = resources.getDimensionPixelSize(R.dimen.usage_graph_dot_interval); + mDottedPaint.setStrokeWidth(dots * 3); + mDottedPaint.setPathEffect(new DashPathEffect(new float[] {dots, interval}, 0)); + mDottedPaint.setColor(context.getColor(R.color.usage_graph_dots)); + + TypedValue v = new TypedValue(); + context.getTheme().resolveAttribute(com.android.internal.R.attr.listDivider, v, true); + mDivider = context.getDrawable(v.resourceId); + mDividerSize = resources.getDimensionPixelSize(R.dimen.usage_graph_divider_size); + } + + void clearPaths() { + mPaths.clear(); + } + + void setMax(int maxX, int maxY) { + mMaxX = maxX; + mMaxY = maxY; + } + + public void addPath(SparseIntArray points) { + for (int i = 0; i < points.size(); i++) { + mPaths.put(points.keyAt(i), points.valueAt(i)); + } + mPaths.put(points.keyAt(points.size() - 1) + 1, PATH_DELIM); + calculateLocalPaths(); + postInvalidate(); + } + + void setAccentColor(int color) { + mAccentColor = color; + mLinePaint.setColor(mAccentColor); + updateGradient(); + postInvalidate(); + } + + void setShowProjection(boolean showProjection, boolean projectUp) { + mShowProjection = showProjection; + mProjectUp = projectUp; + postInvalidate(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + updateGradient(); + calculateLocalPaths(); + } + + private void calculateLocalPaths() { + if (getWidth() == 0) return; + mLocalPaths.clear(); + int pendingXLoc = 0; + int pendingYLoc = PATH_DELIM; + for (int i = 0; i < mPaths.size(); i++) { + int x = mPaths.keyAt(i); + int y = mPaths.valueAt(i); + if (y == PATH_DELIM) { + if (i == mPaths.size() - 1 && pendingYLoc != PATH_DELIM) { + // Connect to the end of the graph. + mLocalPaths.put(pendingXLoc, pendingYLoc); + } + // Clear out any pending points. + pendingYLoc = PATH_DELIM; + mLocalPaths.put(pendingXLoc + 1, PATH_DELIM); + } else { + final int lx = getX(x); + final int ly = getY(y); + pendingXLoc = lx; + if (mLocalPaths.size() > 0) { + int lastX = mLocalPaths.keyAt(mLocalPaths.size() - 1); + int lastY = mLocalPaths.valueAt(mLocalPaths.size() - 1); + if (lastY != PATH_DELIM && (lastX == lx || lastY == ly)) { + pendingYLoc = ly; + continue; + } + } + mLocalPaths.put(lx, ly); + } + } + } + + private int getX(float x) { + return (int) (x / mMaxX * getWidth()); + } + + private int getY(float y) { + return (int) (getHeight() * (1 - (y / mMaxY))); + } + + private void updateGradient() { + mFillPaint.setShader(new LinearGradient(0, 0, 0, getHeight(), + getColor(mAccentColor, .2f), 0, TileMode.CLAMP)); + } + + private int getColor(int color, float alphaScale) { + return (color & (((int) (0xff * alphaScale) << 24) | 0xffffff)); + } + + @Override + protected void onDraw(Canvas canvas) { + // Draw lines across the top, middle, and bottom. + drawDivider(0, canvas); + drawDivider((canvas.getHeight() - mDividerSize) / 2, canvas); + drawDivider(canvas.getHeight() - mDividerSize, canvas); + + if (mLocalPaths.size() == 0) { + return; + } + if (mShowProjection) { + drawProjection(canvas); + } + drawFilledPath(canvas); + drawLinePath(canvas); + } + + private void drawProjection(Canvas canvas) { + mPath.reset(); + int x = mLocalPaths.keyAt(mLocalPaths.size() - 2); + int y = mLocalPaths.valueAt(mLocalPaths.size() - 2); + mPath.moveTo(x, y); + mPath.lineTo(canvas.getWidth(), mProjectUp ? 0 : canvas.getHeight()); + canvas.drawPath(mPath, mDottedPaint); + } + + private void drawLinePath(Canvas canvas) { + mPath.reset(); + mPath.moveTo(mLocalPaths.keyAt(0), mLocalPaths.valueAt(0)); + for (int i = 1; i < mLocalPaths.size(); i++) { + int x = mLocalPaths.keyAt(i); + int y = mLocalPaths.valueAt(i); + if (y == PATH_DELIM) { + if (++i < mLocalPaths.size()) { + mPath.moveTo(mLocalPaths.keyAt(i), mLocalPaths.valueAt(i)); + } + } else { + mPath.lineTo(x, y); + } + } + canvas.drawPath(mPath, mLinePaint); + } + + private void drawFilledPath(Canvas canvas) { + mPath.reset(); + float lastStartX = mLocalPaths.keyAt(0); + mPath.moveTo(mLocalPaths.keyAt(0), mLocalPaths.valueAt(0)); + for (int i = 1; i < mLocalPaths.size(); i++) { + int x = mLocalPaths.keyAt(i); + int y = mLocalPaths.valueAt(i); + if (y == PATH_DELIM) { + mPath.lineTo(mLocalPaths.keyAt(i - 1), getHeight()); + mPath.lineTo(lastStartX, getHeight()); + mPath.close(); + if (++i < mLocalPaths.size()) { + lastStartX = mLocalPaths.keyAt(i); + mPath.moveTo(mLocalPaths.keyAt(i), mLocalPaths.valueAt(i)); + } + } else { + mPath.lineTo(x, y); + } + } + canvas.drawPath(mPath, mFillPaint); + } + + private void drawDivider(int y, Canvas canvas) { + mDivider.setBounds(0, y, canvas.getWidth(), y + mDividerSize); + mDivider.draw(canvas); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/UsageView.java b/packages/SettingsLib/src/com/android/settingslib/graph/UsageView.java new file mode 100644 index 0000000000000..978f16ac95510 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/graph/UsageView.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2016 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.settingslib.graph; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.util.SparseIntArray; +import android.view.LayoutInflater; +import android.widget.FrameLayout; +import android.widget.TextView; +import com.android.settingslib.R; + +public class UsageView extends FrameLayout { + + private final UsageGraph mUsageGraph; + private final TextView[] mLabels; + private final TextView[] mBottomLabels; + + public UsageView(Context context, AttributeSet attrs) { + super(context, attrs); + LayoutInflater.from(context).inflate(R.layout.usage_view, this); + mUsageGraph = (UsageGraph) findViewById(R.id.usage_graph); + mLabels = new TextView[] { + (TextView) findViewById(R.id.label_bottom), + (TextView) findViewById(R.id.label_middle), + (TextView) findViewById(R.id.label_top), + }; + mBottomLabels = new TextView[] { + (TextView) findViewById(R.id.label_start), + (TextView) findViewById(R.id.label_end), + }; + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.UsageView, 0, 0); + if (a.hasValue(R.styleable.UsageView_sideLabels)) { + setSideLabels(a.getTextArray(R.styleable.UsageView_sideLabels)); + } + if (a.hasValue(R.styleable.UsageView_bottomLabels)) { + setBottomLabels(a.getTextArray(R.styleable.UsageView_bottomLabels)); + } + if (a.hasValue(R.styleable.UsageView_textColor)) { + int color = a.getColor(R.styleable.UsageView_textColor, 0); + for (TextView v : mLabels) { + v.setTextColor(color); + } + for (TextView v : mBottomLabels) { + v.setTextColor(color); + } + } + mUsageGraph.setAccentColor(a.getColor(R.styleable.UsageView_android_colorAccent, 0)); + } + + public void clearPaths() { + mUsageGraph.clearPaths(); + } + + public void addPath(SparseIntArray points) { + mUsageGraph.addPath(points); + } + + public void configureGraph(int maxX, int maxY, boolean showProjection, boolean projectUp) { + mUsageGraph.setMax(maxX, maxY); + mUsageGraph.setShowProjection(showProjection, projectUp); + } + + public void setAccentColor(int color) { + mUsageGraph.setAccentColor(color); + } + + public void setSideLabels(CharSequence[] labels) { + if (labels.length != mLabels.length) { + throw new IllegalArgumentException("Invalid number of labels"); + } + for (int i = 0; i < mLabels.length; i++) { + mLabels[i].setText(labels[i]); + } + } + + public void setBottomLabels(CharSequence[] labels) { + if (labels.length != mBottomLabels.length) { + throw new IllegalArgumentException("Invalid number of labels"); + } + for (int i = 0; i < mBottomLabels.length; i++) { + mBottomLabels[i].setText(labels[i]); + } + } + +} diff --git a/packages/SystemUI/res/layout/battery_detail.xml b/packages/SystemUI/res/layout/battery_detail.xml index ea4db4b141822..ded69be41e564 100644 --- a/packages/SystemUI/res/layout/battery_detail.xml +++ b/packages/SystemUI/res/layout/battery_detail.xml @@ -14,51 +14,86 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - + android:orientation="vertical"> + android:paddingStart="72dp" + android:paddingBottom="@dimen/battery_detail_graph_space_top" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="?android:attr/colorAccent" /> - + systemui:sideLabels="@array/battery_labels" + android:colorAccent="?android:attr/colorAccent" + systemui:textColor="#66FFFFFF" /> - + + + android:paddingTop="16dp" + android:paddingBottom="16dp" + android:background="?android:attr/selectableItemBackground" + android:clickable="true"> - + + + + + + + + + + diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index 7e8d802fc947d..6594bd28c8b45 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -19,5 +19,8 @@ card. --> 3 + + false + 3 diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml index be5b856a65f5b..4ed15d5c400b3 100644 --- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml @@ -37,4 +37,7 @@ 162dp 42dp + + 27dp + 27dp diff --git a/packages/SystemUI/res/values-w550dp-land/config.xml b/packages/SystemUI/res/values-w550dp-land/config.xml index 71e54a1ca61c9..16d5317636a23 100644 --- a/packages/SystemUI/res/values-w550dp-land/config.xml +++ b/packages/SystemUI/res/values-w550dp-land/config.xml @@ -20,5 +20,9 @@ + + + true + 4 diff --git a/packages/SystemUI/res/values-w550dp-land/dimens.xml b/packages/SystemUI/res/values-w550dp-land/dimens.xml index 4160c83683ccb..cd17bedcec32c 100644 --- a/packages/SystemUI/res/values-w550dp-land/dimens.xml +++ b/packages/SystemUI/res/values-w550dp-land/dimens.xml @@ -20,4 +20,7 @@ 544dp 32dp + + 9dp + 9dp diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 72421a3070ae2..4e1680d54653f 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -89,6 +89,9 @@ 3 + + false + 3 diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 216d43969d376..11c13e1786259 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -609,4 +609,7 @@ 14.5dp 9.5dp + + 27dp + 27dp diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 5e8c12310bb04..526d842262072 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1281,7 +1281,7 @@ B - Battery (%1$d%%) + Battery usage Battery Saver not available during charging diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java index 72cdf180f999e..6a9d82602fc23 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java @@ -19,15 +19,18 @@ import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.Handler; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.RelativeSizeSpan; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Checkable; import android.widget.ImageView; import android.widget.TextView; - import com.android.internal.logging.MetricsProto.MetricsEvent; import com.android.settingslib.BatteryInfo; +import com.android.settingslib.graph.UsageView; import com.android.systemui.BatteryMeterDrawable; import com.android.systemui.R; import com.android.systemui.qs.QSTile; @@ -161,29 +164,45 @@ public class BatteryTile extends QSTile implements BatteryControll BatteryInfo.getBatteryInfo(mContext, new BatteryInfo.Callback() { @Override public void onBatteryInfoLoaded(BatteryInfo info) { - if (mCurrentView != null && mCharging) { - ((TextView) mCurrentView.findViewById(android.R.id.title)).setText( - info.mChargeLabelString); + if (mCurrentView != null) { + bindBatteryInfo(info); } } }); - ((TextView) mCurrentView.findViewById(android.R.id.summary)).setText( + ((TextView) mCurrentView.findViewById(android.R.id.title)).setText( R.string.battery_detail_charging_summary); - mCurrentView.setClickable(false); mCurrentView.findViewById(android.R.id.icon).setVisibility(View.INVISIBLE); - mCurrentView.findViewById(android.R.id.toggle).setVisibility(View.INVISIBLE); + mCurrentView.findViewById(android.R.id.toggle).setVisibility(View.GONE); + mCurrentView.findViewById(R.id.switch_container).setClickable(false); } else { ((TextView) mCurrentView.findViewById(android.R.id.title)).setText( R.string.battery_detail_switch_title); ((TextView) mCurrentView.findViewById(android.R.id.summary)).setText( R.string.battery_detail_switch_summary); - mCurrentView.setClickable(true); mCurrentView.findViewById(android.R.id.icon).setVisibility(View.VISIBLE); mCurrentView.findViewById(android.R.id.toggle).setVisibility(View.VISIBLE); - mCurrentView.setOnClickListener(this); + mCurrentView.findViewById(R.id.switch_container).setClickable(true); + mCurrentView.findViewById(R.id.switch_container).setOnClickListener(this); } } + private void bindBatteryInfo(BatteryInfo info) { + SpannableStringBuilder builder = new SpannableStringBuilder(); + builder.append(info.batteryPercentString, new RelativeSizeSpan(2), + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + if (info.remainingLabel != null) { + if (mContext.getResources().getBoolean(R.bool.quick_settings_wide)) { + builder.append(' '); + } else { + builder.append('\n'); + } + builder.append(info.remainingLabel); + } + ((TextView) mCurrentView.findViewById(R.id.charge_and_estimation)).setText(builder); + + info.bindHistory((UsageView) mCurrentView.findViewById(R.id.battery_usage)); + } + @Override public void onClick(View v) { mBatteryController.setPowerSaveMode(!mPowerSave);