Merge "Battery graph in QS Detail" into nyc-dev

am: c18766d065

* commit 'c18766d065152ee6c5870a9809b35577f71ed8e1':
  Battery graph in QS Detail
This commit is contained in:
Jason Monk
2016-02-16 21:32:00 +00:00
committed by android-build-merger
19 changed files with 773 additions and 58 deletions

View File

@@ -0,0 +1,20 @@
<!--
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.
-->
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" />

View File

@@ -0,0 +1,20 @@
<!--
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.
-->
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" />

View File

@@ -0,0 +1,86 @@
<!--
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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingEnd="@dimen/usage_graph_padding_end"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/usage_graph_area_height"
android:orientation="horizontal"
android:clipChildren="false"
android:clipToPadding="false">
<LinearLayout
android:layout_width="@dimen/usage_graph_labels_width"
android:layout_height="match_parent"
android:paddingStart="@dimen/usage_graph_labels_padding"
android:orientation="vertical">
<include android:id="@+id/label_top"
layout="@layout/usage_side_label" />
<Space
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
<include android:id="@+id/label_middle"
layout="@layout/usage_side_label" />
<Space
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
<include android:id="@+id/label_bottom"
layout="@layout/usage_side_label" />
</LinearLayout>
<com.android.settingslib.graph.UsageGraph
android:id="@+id/usage_graph"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_marginTop="@dimen/usage_graph_margin_top_bottom"
android:layout_marginBottom="@dimen/usage_graph_margin_top_bottom" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/usage_graph_labels_width"
android:orientation="horizontal">
<include android:id="@+id/label_start"
layout="@layout/usage_side_label" />
<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<include android:id="@+id/label_end"
layout="@layout/usage_side_label" />
</LinearLayout>
</LinearLayout>

View File

@@ -24,4 +24,11 @@
</declare-styleable>
<attr name="wifi_signal" format="reference" />
<declare-styleable name="UsageView">
<attr name="android:colorAccent" />
<attr name="sideLabels" format="reference" />
<attr name="bottomLabels" format="reference" />
<attr name="textColor" format="color" />
</declare-styleable>
</resources>

View File

@@ -16,4 +16,6 @@
<resources>
<color name="disabled_text_color">#66000000</color> <!-- 38% black -->
<color name="usage_graph_dots">#455A64</color>
</resources>

View File

@@ -38,4 +38,19 @@
<dimen name="wifi_preference_badge_padding">8dip</dimen>
<!-- Usage graph dimens -->
<dimen name="usage_graph_area_height">122dp</dimen>
<dimen name="usage_graph_margin_top_bottom">9dp</dimen>
<dimen name="usage_graph_padding_end">24dp</dimen>
<dimen name="usage_graph_labels_width">72dp</dimen>
<dimen name="usage_graph_labels_padding">16dp</dimen>
<dimen name="usage_graph_divider_size">1dp</dimen>
<dimen name="usage_graph_line_width">3dp</dimen>
<dimen name="usage_graph_line_corner_radius">6dp</dimen>
<dimen name="usage_graph_dot_size">.75dp</dimen>
<dimen name="usage_graph_dot_interval">7dp</dimen>
</resources>

View File

@@ -732,6 +732,9 @@
<!-- Summary shown for color space correction preference when its value is overridden by another preference [CHAR LIMIT=35] -->
<string name="daltonizer_type_overridden">Overridden by <xliff:g id="title" example="Simulate color space">%1$s</xliff:g></string>
<!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging/discharging -->
<string name="power_remaining_duration_only">Approx. <xliff:g id="time">%2$s</xliff:g> left</string>
<!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration -->
<string name="power_discharging_duration"><xliff:g id="level">%1$s</xliff:g>
- approx. <xliff:g id="time">%2$s</xliff:g> left</string>
@@ -775,4 +778,16 @@
<!-- Option in navigation drawer that leads to Settings main screen [CHAR LIMIT=30] -->
<string name="home">Home</string>
<string-array name="battery_labels" translatable="false">
<item>0%</item>
<item>50%</item>
<item>100%</item>
</string-array>
<!-- Label for length of time since the battery graph started [CHAR LIMIT=20] -->
<string name="charge_length_format"><xliff:g name="time" example="3 hours">%1$s</xliff:g> ago</string>
<!-- Label for length of time until battery is charged [CHAR LIMIT=20] -->
<string name="remaining_length_format"><xliff:g name="time" example="3 hours">%1$s</xliff:g> left</string>
</resources>

View File

@@ -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<Void, Void, BatteryStats>() {
@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;

View File

@@ -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);
}
}

View File

@@ -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]);
}
}
}

View File

@@ -14,51 +14,86 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true">
<ImageView
android:id="@android:id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:layout_marginStart="16dp"
android:layout_marginEnd="32dp" />
android:orientation="vertical">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:id="@+id/charge_and_estimation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toStartOf="@android:id/toggle"
android:layout_toEndOf="@android:id/icon"
android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary"
android:text="@string/battery_detail_switch_title" />
android:paddingStart="72dp"
android:paddingBottom="@dimen/battery_detail_graph_space_top"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/colorAccent" />
<TextView
android:id="@android:id/summary"
android:layout_width="wrap_content"
<com.android.settingslib.graph.UsageView
android:id="@+id/battery_usage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@android:id/title"
android:layout_toStartOf="@android:id/toggle"
android:layout_toEndOf="@android:id/icon"
android:textAppearance="@style/TextAppearance.QS.DetailItemSecondary"
android:text="@string/battery_detail_switch_summary" />
systemui:sideLabels="@array/battery_labels"
android:colorAccent="?android:attr/colorAccent"
systemui:textColor="#66FFFFFF" />
<Switch
android:id="@android:id/toggle"
android:layout_width="wrap_content"
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider"
android:layout_marginTop="@dimen/battery_detail_graph_space_bottom"
android:layout_marginBottom="8dp" />
<RelativeLayout
android:id="@+id/switch_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_marginEnd="16dp"
android:clickable="false"
android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true">
</RelativeLayout>
<ImageView
android:id="@android:id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:layout_marginStart="16dp"
android:layout_marginEnd="32dp" />
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toStartOf="@android:id/toggle"
android:layout_toEndOf="@android:id/icon"
android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary"
android:text="@string/battery_detail_switch_title" />
<TextView
android:id="@android:id/summary"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@android:id/title"
android:layout_toStartOf="@android:id/toggle"
android:layout_toEndOf="@android:id/icon"
android:textAppearance="@style/TextAppearance.QS.DetailItemSecondary"
android:text="@string/battery_detail_switch_summary" />
<Switch
android:id="@android:id/toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_marginEnd="16dp"
android:clickable="false"
android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
</RelativeLayout>
</LinearLayout>

View File

@@ -19,5 +19,8 @@
card. -->
<integer name="keyguard_max_notification_count">3</integer>
<!-- Whether QuickSettings is in a phone landscape -->
<bool name="quick_settings_wide">false</bool>
<integer name="quick_settings_num_columns">3</integer>
</resources>

View File

@@ -37,4 +37,7 @@
<dimen name="navigation_key_width">162dp</dimen>
<dimen name="navigation_key_padding">42dp</dimen>
<dimen name="battery_detail_graph_space_top">27dp</dimen>
<dimen name="battery_detail_graph_space_bottom">27dp</dimen>
</resources>

View File

@@ -20,5 +20,9 @@
<!-- These resources are around just to allow their values to be customized
for different hardware and product builds. -->
<resources>
<!-- Whether QuickSettings is in a phone landscape -->
<bool name="quick_settings_wide">true</bool>
<integer name="quick_settings_num_columns">4</integer>
</resources>

View File

@@ -20,4 +20,7 @@
<dimen name="notification_panel_width">544dp</dimen>
<dimen name="qs_expand_margin">32dp</dimen>
<dimen name="battery_detail_graph_space_top">9dp</dimen>
<dimen name="battery_detail_graph_space_bottom">9dp</dimen>
</resources>

View File

@@ -89,6 +89,9 @@
<!-- Min alpha % that recent items will fade to while being dismissed -->
<integer name="config_recent_item_min_alpha">3</integer>
<!-- Whether QuickSettings is in a phone landscape -->
<bool name="quick_settings_wide">false</bool>
<!-- The number of columns in the QuickSettings -->
<integer name="quick_settings_num_columns">3</integer>

View File

@@ -609,4 +609,7 @@
<dimen name="battery_height">14.5dp</dimen>
<dimen name="battery_width">9.5dp</dimen>
<dimen name="battery_detail_graph_space_top">27dp</dimen>
<dimen name="battery_detail_graph_space_bottom">27dp</dimen>
</resources>

View File

@@ -1280,7 +1280,7 @@
<string name="color_modification_b" translatable="false">B</string>
<!-- Title of the battery settings detail panel [CHAR LIMIT=20] -->
<string name="battery_panel_title">Battery (<xliff:g name="pattery_percent" example="52">%1$d</xliff:g>%%)</string>
<string name="battery_panel_title">Battery usage</string>
<!-- Summary of battery saver not available [CHAR LIMIT=NONE] -->
<string name="battery_detail_charging_summary">Battery Saver not available during charging</string>

View File

@@ -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<QSTile.State> 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);