Merge "Battery graph in QS Detail" into nyc-dev
am: c18766d065
* commit 'c18766d065152ee6c5870a9809b35577f71ed8e1':
Battery graph in QS Detail
This commit is contained in:
20
packages/SettingsLib/res/layout/usage_bottom_label.xml
Normal file
20
packages/SettingsLib/res/layout/usage_bottom_label.xml
Normal 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" />
|
||||
20
packages/SettingsLib/res/layout/usage_side_label.xml
Normal file
20
packages/SettingsLib/res/layout/usage_side_label.xml
Normal 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" />
|
||||
86
packages/SettingsLib/res/layout/usage_view.xml
Normal file
86
packages/SettingsLib/res/layout/usage_view.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -16,4 +16,6 @@
|
||||
|
||||
<resources>
|
||||
<color name="disabled_text_color">#66000000</color> <!-- 38% black -->
|
||||
|
||||
<color name="usage_graph_dots">#455A64</color>
|
||||
</resources>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user