Data usage app bars, draw estimated cycle usage.
Show application list with normalized percentage data usage as horizontal bar chart. Draw estimated usage for remainder of cycle to reinforce log scale, and break out received/sent when showing app details. Bug: 5038591, 4948713, 4818025 Change-Id: Ib71e73d4f692adc7fe8ae87edd0cec2a8f3976d3
This commit is contained in:
37
res/drawable/data_usage_bar.xml
Normal file
37
res/drawable/data_usage_bar.xml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2011 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:id="@android:id/background">
|
||||||
|
<shape>
|
||||||
|
<solid android:color="#ff28262c" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item android:id="@android:id/secondaryProgress">
|
||||||
|
<clip>
|
||||||
|
<shape>
|
||||||
|
<solid android:color="#c050ade5" />
|
||||||
|
</shape>
|
||||||
|
</clip>
|
||||||
|
</item>
|
||||||
|
<item android:id="@android:id/progress">
|
||||||
|
<clip>
|
||||||
|
<shape>
|
||||||
|
<solid android:color="#c050ade5" />
|
||||||
|
</shape>
|
||||||
|
</clip>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
@@ -14,26 +14,34 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="48dip"
|
android:padding="8dip"
|
||||||
android:orientation="vertical">
|
android:columnCount="2">
|
||||||
|
|
||||||
|
<!-- TODO: consider using canShrink -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@android:id/text1"
|
android:id="@android:id/title"
|
||||||
android:layout_width="match_parent"
|
android:singleLine="true"
|
||||||
android:layout_height="wrap_content"
|
android:ellipsize="marquee"
|
||||||
android:layout_marginLeft="6dip"
|
android:layout_columnFlexibility="canStretch"
|
||||||
android:layout_marginTop="6dip"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@android:id/text2"
|
android:id="@android:id/summary"
|
||||||
android:layout_width="match_parent"
|
android:layout_gravity="right"
|
||||||
android:layout_height="wrap_content"
|
android:layout_marginLeft="8dip"
|
||||||
android:layout_marginLeft="6dip"
|
|
||||||
android:layout_marginBottom="6dip"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||||
|
|
||||||
</LinearLayout>
|
<ProgressBar
|
||||||
|
android:id="@android:id/progress"
|
||||||
|
android:layout_height="12dip"
|
||||||
|
android:layout_columnSpan="2"
|
||||||
|
android:layout_gravity="fill_horizontal"
|
||||||
|
android:layout_marginTop="4dip"
|
||||||
|
android:max="100"
|
||||||
|
android:progressDrawable="@drawable/data_usage_bar"
|
||||||
|
style="?android:attr/progressBarStyleHorizontal" />
|
||||||
|
|
||||||
|
</GridLayout>
|
||||||
|
@@ -3475,6 +3475,8 @@ found in the list of installed applications.</string>
|
|||||||
|
|
||||||
<!-- Title of data usage item that represents all uninstalled applications. [CHAR LIMIT=48] -->
|
<!-- Title of data usage item that represents all uninstalled applications. [CHAR LIMIT=48] -->
|
||||||
<string name="data_usage_uninstalled_apps">Removed apps</string>
|
<string name="data_usage_uninstalled_apps">Removed apps</string>
|
||||||
|
<!-- Combination of total network bytes sent and received by an application. [CHAR LIMIT=NONE] -->
|
||||||
|
<string name="data_usage_received_sent"><xliff:g id="received" example="128KB">%1$s</xliff:g> received, <xliff:g id="sent" example="1.3GB">%2$s</xliff:g> sent</string>
|
||||||
|
|
||||||
<!-- Button at the bottom of the CryptKeeper screen to make an emergency call. -->
|
<!-- Button at the bottom of the CryptKeeper screen to make an emergency call. -->
|
||||||
<string name="cryptkeeper_emergency_call">Emergency call</string>
|
<string name="cryptkeeper_emergency_call">Emergency call</string>
|
||||||
|
@@ -97,6 +97,7 @@ import android.widget.CompoundButton.OnCheckedChangeListener;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.NumberPicker;
|
import android.widget.NumberPicker;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
import android.widget.Switch;
|
import android.widget.Switch;
|
||||||
import android.widget.TabHost;
|
import android.widget.TabHost;
|
||||||
@@ -946,8 +947,11 @@ public class DataUsageSummary extends Fragment {
|
|||||||
final long now = System.currentTimeMillis();
|
final long now = System.currentTimeMillis();
|
||||||
final NetworkStatsHistory.Entry entry = mDetailHistory.getValues(
|
final NetworkStatsHistory.Entry entry = mDetailHistory.getValues(
|
||||||
start, end, now, null);
|
start, end, now, null);
|
||||||
final long total = entry.rxBytes + entry.txBytes;
|
|
||||||
mAppSubtitle.setText(Formatter.formatFileSize(context, total));
|
mAppSubtitle.setText(
|
||||||
|
getString(R.string.data_usage_received_sent,
|
||||||
|
Formatter.formatFileSize(context, entry.rxBytes),
|
||||||
|
Formatter.formatFileSize(context, entry.txBytes)));
|
||||||
}
|
}
|
||||||
|
|
||||||
getLoaderManager().destroyLoader(LOADER_SUMMARY);
|
getLoaderManager().destroyLoader(LOADER_SUMMARY);
|
||||||
@@ -1110,6 +1114,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
*/
|
*/
|
||||||
public static class DataUsageAdapter extends BaseAdapter {
|
public static class DataUsageAdapter extends BaseAdapter {
|
||||||
private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
|
private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
|
||||||
|
private long mLargest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bind the given {@link NetworkStats}, or {@code null} to clear list.
|
* Bind the given {@link NetworkStats}, or {@code null} to clear list.
|
||||||
@@ -1143,6 +1148,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Collections.sort(mItems);
|
Collections.sort(mItems);
|
||||||
|
mLargest = (mItems.size() > 0) ? mItems.get(0).total : 0;
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1170,12 +1176,17 @@ public class DataUsageSummary extends Fragment {
|
|||||||
|
|
||||||
final Context context = parent.getContext();
|
final Context context = parent.getContext();
|
||||||
|
|
||||||
final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
|
final TextView title = (TextView) convertView.findViewById(android.R.id.title);
|
||||||
final TextView text2 = (TextView) convertView.findViewById(android.R.id.text2);
|
final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
|
||||||
|
final ProgressBar progress = (ProgressBar) convertView.findViewById(
|
||||||
|
android.R.id.progress);
|
||||||
|
|
||||||
final AppUsageItem item = mItems.get(position);
|
final AppUsageItem item = mItems.get(position);
|
||||||
text1.setText(resolveLabelForUid(context, item.uid));
|
title.setText(resolveLabelForUid(context, item.uid));
|
||||||
text2.setText(Formatter.formatFileSize(context, item.total));
|
summary.setText(Formatter.formatFileSize(context, item.total));
|
||||||
|
|
||||||
|
final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0;
|
||||||
|
progress.setProgress(percentTotal);
|
||||||
|
|
||||||
return convertView;
|
return convertView;
|
||||||
}
|
}
|
||||||
@@ -1187,7 +1198,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
* {@link DataUsageSummary}.
|
* {@link DataUsageSummary}.
|
||||||
*/
|
*/
|
||||||
public static class AppDetailsFragment extends Fragment {
|
public static class AppDetailsFragment extends Fragment {
|
||||||
public static final String EXTRA_UID = "uid";
|
private static final String EXTRA_UID = "uid";
|
||||||
|
|
||||||
public static void show(DataUsageSummary parent, int uid) {
|
public static void show(DataUsageSummary parent, int uid) {
|
||||||
final Bundle args = new Bundle();
|
final Bundle args = new Bundle();
|
||||||
@@ -1225,8 +1236,8 @@ public class DataUsageSummary extends Fragment {
|
|||||||
* {@link NetworkPolicy#limitBytes}.
|
* {@link NetworkPolicy#limitBytes}.
|
||||||
*/
|
*/
|
||||||
public static class ConfirmLimitFragment extends DialogFragment {
|
public static class ConfirmLimitFragment extends DialogFragment {
|
||||||
public static final String EXTRA_MESSAGE_ID = "messageId";
|
private static final String EXTRA_MESSAGE_ID = "messageId";
|
||||||
public static final String EXTRA_LIMIT_BYTES = "limitBytes";
|
private static final String EXTRA_LIMIT_BYTES = "limitBytes";
|
||||||
|
|
||||||
public static void show(DataUsageSummary parent) {
|
public static void show(DataUsageSummary parent) {
|
||||||
final Bundle args = new Bundle();
|
final Bundle args = new Bundle();
|
||||||
@@ -1278,7 +1289,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
* Dialog to edit {@link NetworkPolicy#cycleDay}.
|
* Dialog to edit {@link NetworkPolicy#cycleDay}.
|
||||||
*/
|
*/
|
||||||
public static class CycleEditorFragment extends DialogFragment {
|
public static class CycleEditorFragment extends DialogFragment {
|
||||||
public static final String EXTRA_CYCLE_DAY = "cycleDay";
|
private static final String EXTRA_CYCLE_DAY = "cycleDay";
|
||||||
|
|
||||||
public static void show(DataUsageSummary parent) {
|
public static void show(DataUsageSummary parent) {
|
||||||
final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate);
|
final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate);
|
||||||
@@ -1331,7 +1342,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
* and giving the user an option to bypass.
|
* and giving the user an option to bypass.
|
||||||
*/
|
*/
|
||||||
public static class PolicyLimitFragment extends DialogFragment {
|
public static class PolicyLimitFragment extends DialogFragment {
|
||||||
public static final String EXTRA_TITLE_ID = "titleId";
|
private static final String EXTRA_TITLE_ID = "titleId";
|
||||||
|
|
||||||
public static void show(DataUsageSummary parent) {
|
public static void show(DataUsageSummary parent) {
|
||||||
final Bundle args = new Bundle();
|
final Bundle args = new Bundle();
|
||||||
|
@@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
package com.android.settings.widget;
|
package com.android.settings.widget;
|
||||||
|
|
||||||
|
import static android.text.format.DateUtils.DAY_IN_MILLIS;
|
||||||
|
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
@@ -46,11 +49,13 @@ public class ChartNetworkSeriesView extends View {
|
|||||||
private Paint mPaintStroke;
|
private Paint mPaintStroke;
|
||||||
private Paint mPaintFill;
|
private Paint mPaintFill;
|
||||||
private Paint mPaintFillSecondary;
|
private Paint mPaintFillSecondary;
|
||||||
|
private Paint mPaintEstimate;
|
||||||
|
|
||||||
private NetworkStatsHistory mStats;
|
private NetworkStatsHistory mStats;
|
||||||
|
|
||||||
private Path mPathStroke;
|
private Path mPathStroke;
|
||||||
private Path mPathFill;
|
private Path mPathFill;
|
||||||
|
private Path mPathEstimate;
|
||||||
|
|
||||||
private long mPrimaryLeft;
|
private long mPrimaryLeft;
|
||||||
private long mPrimaryRight;
|
private long mPrimaryRight;
|
||||||
@@ -81,6 +86,7 @@ public class ChartNetworkSeriesView extends View {
|
|||||||
|
|
||||||
mPathStroke = new Path();
|
mPathStroke = new Path();
|
||||||
mPathFill = new Path();
|
mPathFill = new Path();
|
||||||
|
mPathEstimate = new Path();
|
||||||
}
|
}
|
||||||
|
|
||||||
void init(ChartAxis horiz, ChartAxis vert) {
|
void init(ChartAxis horiz, ChartAxis vert) {
|
||||||
@@ -104,6 +110,12 @@ public class ChartNetworkSeriesView extends View {
|
|||||||
mPaintFillSecondary.setColor(fillSecondary);
|
mPaintFillSecondary.setColor(fillSecondary);
|
||||||
mPaintFillSecondary.setStyle(Style.FILL);
|
mPaintFillSecondary.setStyle(Style.FILL);
|
||||||
mPaintFillSecondary.setAntiAlias(true);
|
mPaintFillSecondary.setAntiAlias(true);
|
||||||
|
|
||||||
|
mPaintEstimate = new Paint();
|
||||||
|
mPaintEstimate.setStrokeWidth(3.0f);
|
||||||
|
mPaintEstimate.setColor(fillSecondary);
|
||||||
|
mPaintEstimate.setStyle(Style.STROKE);
|
||||||
|
mPaintEstimate.setAntiAlias(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bindNetworkStats(NetworkStatsHistory stats) {
|
public void bindNetworkStats(NetworkStatsHistory stats) {
|
||||||
@@ -111,6 +123,7 @@ public class ChartNetworkSeriesView extends View {
|
|||||||
|
|
||||||
mPathStroke.reset();
|
mPathStroke.reset();
|
||||||
mPathFill.reset();
|
mPathFill.reset();
|
||||||
|
mPathEstimate.reset();
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,6 +151,7 @@ public class ChartNetworkSeriesView extends View {
|
|||||||
|
|
||||||
mPathStroke.reset();
|
mPathStroke.reset();
|
||||||
mPathFill.reset();
|
mPathFill.reset();
|
||||||
|
mPathEstimate.reset();
|
||||||
|
|
||||||
// bail when not enough stats to render
|
// bail when not enough stats to render
|
||||||
if (mStats == null || mStats.size() < 2) return;
|
if (mStats == null || mStats.size() < 2) return;
|
||||||
@@ -149,6 +163,7 @@ public class ChartNetworkSeriesView extends View {
|
|||||||
float firstX = 0;
|
float firstX = 0;
|
||||||
float lastX = 0;
|
float lastX = 0;
|
||||||
float lastY = 0;
|
float lastY = 0;
|
||||||
|
long lastTime = Long.MIN_VALUE;
|
||||||
|
|
||||||
// TODO: count fractional data from first bucket crossing start;
|
// TODO: count fractional data from first bucket crossing start;
|
||||||
// currently it only accepts first full bucket.
|
// currently it only accepts first full bucket.
|
||||||
@@ -159,6 +174,7 @@ public class ChartNetworkSeriesView extends View {
|
|||||||
for (int i = 0; i < mStats.size(); i++) {
|
for (int i = 0; i < mStats.size(); i++) {
|
||||||
entry = mStats.getValues(i, entry);
|
entry = mStats.getValues(i, entry);
|
||||||
|
|
||||||
|
lastTime = entry.bucketStart;
|
||||||
final float x = mHoriz.convertToPoint(entry.bucketStart);
|
final float x = mHoriz.convertToPoint(entry.bucketStart);
|
||||||
final float y = mVert.convertToPoint(totalData);
|
final float y = mVert.convertToPoint(totalData);
|
||||||
|
|
||||||
@@ -193,6 +209,35 @@ public class ChartNetworkSeriesView extends View {
|
|||||||
// drop to bottom of graph from current location
|
// drop to bottom of graph from current location
|
||||||
mPathFill.lineTo(lastX, height);
|
mPathFill.lineTo(lastX, height);
|
||||||
mPathFill.lineTo(firstX, height);
|
mPathFill.lineTo(firstX, height);
|
||||||
|
|
||||||
|
// build estimated data
|
||||||
|
mPathEstimate.moveTo(lastX, lastY);
|
||||||
|
|
||||||
|
final long now = System.currentTimeMillis();
|
||||||
|
final long bucketDuration = mStats.getBucketDuration();
|
||||||
|
|
||||||
|
// long window is average over two weeks
|
||||||
|
entry = mStats.getValues(lastTime - WEEK_IN_MILLIS * 2, lastTime, now, entry);
|
||||||
|
final long longWindow = (entry.rxBytes + entry.txBytes) * bucketDuration
|
||||||
|
/ entry.bucketDuration;
|
||||||
|
|
||||||
|
long futureTime = 0;
|
||||||
|
while (lastX < width) {
|
||||||
|
futureTime += bucketDuration;
|
||||||
|
|
||||||
|
// short window is day average last week
|
||||||
|
final long lastWeekTime = lastTime - WEEK_IN_MILLIS + (futureTime % WEEK_IN_MILLIS);
|
||||||
|
entry = mStats.getValues(lastWeekTime - DAY_IN_MILLIS, lastWeekTime, now, entry);
|
||||||
|
final long shortWindow = (entry.rxBytes + entry.txBytes) * bucketDuration
|
||||||
|
/ entry.bucketDuration;
|
||||||
|
|
||||||
|
totalData += (longWindow * 7 + shortWindow * 3) / 10;
|
||||||
|
|
||||||
|
lastX = mHoriz.convertToPoint(lastTime + futureTime);
|
||||||
|
final float y = mVert.convertToPoint(totalData);
|
||||||
|
|
||||||
|
mPathEstimate.lineTo(lastX, y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -202,6 +247,11 @@ public class ChartNetworkSeriesView extends View {
|
|||||||
final float primaryLeftPoint = mHoriz.convertToPoint(mPrimaryLeft);
|
final float primaryLeftPoint = mHoriz.convertToPoint(mPrimaryLeft);
|
||||||
final float primaryRightPoint = mHoriz.convertToPoint(mPrimaryRight);
|
final float primaryRightPoint = mHoriz.convertToPoint(mPrimaryRight);
|
||||||
|
|
||||||
|
save = canvas.save();
|
||||||
|
canvas.clipRect(0, 0, getWidth(), getHeight());
|
||||||
|
canvas.drawPath(mPathEstimate, mPaintEstimate);
|
||||||
|
canvas.restoreToCount(save);
|
||||||
|
|
||||||
save = canvas.save();
|
save = canvas.save();
|
||||||
canvas.clipRect(0, 0, primaryLeftPoint, getHeight());
|
canvas.clipRect(0, 0, primaryLeftPoint, getHeight());
|
||||||
canvas.drawPath(mPathFill, mPaintFillSecondary);
|
canvas.drawPath(mPathFill, mPaintFillSecondary);
|
||||||
|
Reference in New Issue
Block a user