Data usage app icons and details, chart labels.

Add app icons into both summary list and details pane. Also show list
of all applications merged under a UID. Draw dates on chart axis, and
avoid flashing policy sweeps when switching networks in detail mode.

Bug: 5087283, 5038812
Change-Id: I1dcd03ca85b517f8726452af8a46b4be9b3d20f1
This commit is contained in:
Jeff Sharkey
2011-08-04 21:17:23 -07:00
parent ce919e40c9
commit d39c6e4083
11 changed files with 183 additions and 57 deletions

View File

@@ -1185,7 +1185,8 @@
</activity>
<activity android:name="Settings$DataUsageSummaryActivity"
android:label="@string/data_usage_summary_title">
android:label="@string/data_usage_summary_title"
android:uiOptions="none">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />

View File

@@ -0,0 +1,22 @@
<?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.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium" />

View File

@@ -30,7 +30,7 @@
settings:primaryDrawable="@drawable/data_grid_primary"
settings:secondaryDrawable="@drawable/data_grid_secondary"
settings:borderDrawable="@drawable/data_grid_border"
settings:labelColor="#24aae1" />
settings:labelColor="#667bb5" />
<com.android.settings.widget.ChartNetworkSeriesView
android:id="@+id/series"

View File

@@ -20,17 +20,20 @@
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/app_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dip" />
<ImageView
android:id="@+id/app_icon"
android:layout_width="48dip"
android:layout_height="48dip"
android:layout_marginLeft="16dip"
android:scaleType="centerInside" />
<TextView
android:id="@+id/app_subtitle"
<LinearLayout
android:id="@+id/app_titles"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dip" />
android:layout_height="match_parent"
android:layout_marginLeft="16dip"
android:layout_marginTop="8dip"
android:orientation="vertical" />
<Button
android:id="@+id/app_settings"

View File

@@ -43,6 +43,8 @@
android:paddingRight="16dip"
android:paddingTop="8dip"
android:paddingBottom="8dip"
android:singleLine="true"
android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView

View File

@@ -21,7 +21,15 @@
android:paddingRight="16dip"
android:paddingTop="8dip"
android:paddingBottom="8dip"
android:columnCount="2">
android:columnCount="3">
<ImageView
android:id="@android:id/icon"
android:layout_width="48dip"
android:layout_height="48dip"
android:layout_rowSpan="2"
android:layout_marginRight="8dip"
android:scaleType="centerInside" />
<TextView
android:id="@android:id/title"

View File

@@ -3465,7 +3465,7 @@ found in the list of installed applications.</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>
<!-- Label displaying total network data transferred during a specific time period. [CHAR LIMIT=64] -->
<string name="data_usage_total_during_range">Data usage: <xliff:g id="total" example="128KB">%1$s</xliff:g> between <xliff:g id="range" example="Jul 1 - Jul 31">%2$s</xliff:g></string>
<string name="data_usage_total_during_range">Data usage: <xliff:g id="total" example="128KB">%1$s</xliff:g> on <xliff:g id="range" example="Jul 1 - Jul 31">%2$s</xliff:g></string>
<!-- Button at the bottom of the CryptKeeper screen to make an emergency call. -->
<string name="cryptkeeper_emergency_call">Emergency call</string>

View File

@@ -37,6 +37,9 @@ import static android.net.NetworkTemplate.buildTemplateMobile3gLower;
import static android.net.NetworkTemplate.buildTemplateMobile4g;
import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.net.NetworkTemplate.buildTemplateWifi;
import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
import static android.text.format.Time.TIMEZONE_UTC;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.animation.LayoutTransition;
@@ -57,6 +60,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
@@ -78,7 +82,6 @@ import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.format.Formatter;
import android.text.format.Time;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -97,6 +100,7 @@ import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.NumberPicker;
@@ -190,8 +194,8 @@ public class DataUsageSummary extends Fragment {
private TextView mEmpty;
private View mAppDetail;
private TextView mAppTitle;
private TextView mAppSubtitle;
private ImageView mAppIcon;
private ViewGroup mAppTitles;
private Button mAppSettings;
private LinearLayout mAppSwitches;
@@ -295,8 +299,8 @@ public class DataUsageSummary extends Fragment {
{
// bind app detail controls
mAppDetail = mHeader.findViewById(R.id.app_detail);
mAppTitle = (TextView) mAppDetail.findViewById(R.id.app_title);
mAppSubtitle = (TextView) mAppDetail.findViewById(R.id.app_subtitle);
mAppIcon = (ImageView) mAppDetail.findViewById(R.id.app_icon);
mAppTitles = (ViewGroup) mAppDetail.findViewById(R.id.app_titles);
mAppSwitches = (LinearLayout) mAppDetail.findViewById(R.id.app_switches);
mAppSettings = (Button) mAppDetail.findViewById(R.id.app_settings);
@@ -643,6 +647,10 @@ public class DataUsageSummary extends Fragment {
* depending on {@link #isAppDetailMode()}.
*/
private void updateAppDetail() {
final Context context = getActivity();
final PackageManager pm = context.getPackageManager();
final LayoutInflater inflater = getActivity().getLayoutInflater();
if (isAppDetailMode()) {
mAppDetail.setVisibility(View.VISIBLE);
mCycleAdapter.setChangeVisible(false);
@@ -658,8 +666,18 @@ public class DataUsageSummary extends Fragment {
// remove warning/limit sweeps while in detail mode
mChart.bindNetworkPolicy(null);
final PackageManager pm = getActivity().getPackageManager();
mAppTitle.setText(pm.getNameForUid(mUid));
// show icon and all labels appearing under this app
final UidDetail detail = resolveDetailForUid(context, mUid);
mAppIcon.setImageDrawable(detail.icon);
mAppTitles.removeAllViews();
if (detail.detailLabels != null) {
for (CharSequence label : detail.detailLabels) {
mAppTitles.addView(inflateAppTitle(inflater, mAppTitles, label));
}
} else {
mAppTitles.addView(inflateAppTitle(inflater, mAppTitles, detail.label));
}
// enable settings button when package provides it
// TODO: target torwards entire UID instead of just first package
@@ -693,7 +711,6 @@ public class DataUsageSummary extends Fragment {
updateDetailData();
final Context context = getActivity();
if (NetworkPolicyManager.isUidValidForPolicy(context, mUid) && !getRestrictBackground()
&& isBandwidthControlEnabled()) {
mAppRestrictView.setVisibility(View.VISIBLE);
@@ -814,7 +831,9 @@ public class DataUsageSummary extends Fragment {
if (isNetworkPolicyModifiable()) {
mDisableAtLimitView.setVisibility(View.VISIBLE);
mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
mChart.bindNetworkPolicy(policy);
if (!isAppDetailMode()) {
mChart.bindNetworkPolicy(policy);
}
} else {
// controls are disabled; don't bind warning/limit sweeps
@@ -998,11 +1017,6 @@ public class DataUsageSummary extends Fragment {
if (isAppDetailMode()) {
if (mDetailHistory != null) {
entry = mDetailHistory.getValues(start, end, now, null);
mAppSubtitle.setText(
getString(R.string.data_usage_received_sent,
Formatter.formatFileSize(context, entry.rxBytes),
Formatter.formatFileSize(context, entry.txBytes)));
} else {
entry = null;
}
@@ -1015,12 +1029,11 @@ public class DataUsageSummary extends Fragment {
// kick off loader for detailed stats
getLoaderManager().restartLoader(LOADER_SUMMARY,
SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryForAllUid);
}
final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0;
final String totalPhrase = Formatter.formatFileSize(context, totalBytes);
final String rangePhrase = formatDateRange(context, start, end, null);
final String rangePhrase = formatDateRange(context, start, end, false);
mUsageSummary.setText(
getString(R.string.data_usage_total_during_range, totalPhrase, rangePhrase));
@@ -1104,7 +1117,7 @@ public class DataUsageSummary extends Fragment {
}
public CycleItem(Context context, long start, long end) {
this.label = formatDateRange(context, start, end, Time.TIMEZONE_UTC);
this.label = formatDateRange(context, start, end, true);
this.start = start;
this.end = end;
}
@@ -1119,14 +1132,11 @@ public class DataUsageSummary extends Fragment {
private static final java.util.Formatter sFormatter = new java.util.Formatter(
sBuilder, Locale.getDefault());
private static String formatDateRange(Context context, long start, long end, String timezone) {
synchronized (sBuilder) {
int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH;
if (Time.getJulianDay(start, 0) == Time.getJulianDay(end, 0)) {
// when times are on same day, include time detail
flags |= DateUtils.FORMAT_SHOW_TIME;
}
public static String formatDateRange(Context context, long start, long end, boolean utcTime) {
final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
final String timezone = utcTime ? TIMEZONE_UTC : null;
synchronized (sBuilder) {
sBuilder.setLength(0);
return DateUtils
.formatDateRange(context, sFormatter, start, end, flags, timezone).toString();
@@ -1250,13 +1260,17 @@ public class DataUsageSummary extends Fragment {
final Context context = parent.getContext();
final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
final TextView title = (TextView) convertView.findViewById(android.R.id.title);
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);
title.setText(resolveLabelForUid(context, item.uid));
final UidDetail detail = resolveDetailForUid(context, item.uid);
icon.setImageDrawable(detail.icon);
title.setText(detail.label);
summary.setText(Formatter.formatFileSize(context, item.total));
final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0;
@@ -1536,48 +1550,66 @@ public class DataUsageSummary extends Fragment {
}
}
public static class UidDetail {
public CharSequence label;
public CharSequence[] detailLabels;
public Drawable icon;
}
/**
* Resolve best descriptive label for the given UID.
*/
public static CharSequence resolveLabelForUid(Context context, int uid) {
public static UidDetail resolveDetailForUid(Context context, int uid) {
final Resources res = context.getResources();
final PackageManager pm = context.getPackageManager();
final UidDetail detail = new UidDetail();
detail.label = pm.getNameForUid(uid);
detail.icon = pm.getDefaultActivityIcon();
// handle special case labels
switch (uid) {
case android.os.Process.SYSTEM_UID:
return res.getText(R.string.process_kernel_label);
detail.label = res.getString(R.string.process_kernel_label);
detail.icon = pm.getDefaultActivityIcon();
return detail;
case TrafficStats.UID_REMOVED:
return res.getText(R.string.data_usage_uninstalled_apps);
detail.label = res.getString(R.string.data_usage_uninstalled_apps);
detail.icon = pm.getDefaultActivityIcon();
return detail;
}
// otherwise fall back to using packagemanager labels
final String[] packageNames = pm.getPackagesForUid(uid);
final int length = packageNames != null ? packageNames.length : 0;
CharSequence label = pm.getNameForUid(uid);
try {
if (length == 1) {
final ApplicationInfo info = pm.getApplicationInfo(packageNames[0], 0);
label = info.loadLabel(pm);
detail.label = info.loadLabel(pm).toString();
detail.icon = info.loadIcon(pm);
} else if (length > 1) {
for (String packageName : packageNames) {
final PackageInfo info = pm.getPackageInfo(packageName, 0);
if (info.sharedUserLabel != 0) {
label = pm.getText(packageName, info.sharedUserLabel, info.applicationInfo);
if (!TextUtils.isEmpty(label)) {
break;
}
detail.detailLabels = new CharSequence[length];
for (int i = 0; i < length; i++) {
final String packageName = packageNames[i];
final PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
final ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
detail.detailLabels[i] = appInfo.loadLabel(pm).toString();
if (packageInfo.sharedUserLabel != 0) {
detail.label = pm.getText(packageName, packageInfo.sharedUserLabel,
packageInfo.applicationInfo).toString();
detail.icon = appInfo.loadIcon(pm);
}
}
}
} catch (NameNotFoundException e) {
}
if (TextUtils.isEmpty(label)) {
label = Integer.toString(uid);
if (TextUtils.isEmpty(detail.label)) {
detail.label = Integer.toString(uid);
}
return label;
return detail;
}
/**
@@ -1652,6 +1684,14 @@ public class DataUsageSummary extends Fragment {
return view;
}
private static View inflateAppTitle(
LayoutInflater inflater, ViewGroup root, CharSequence label) {
final TextView view = (TextView) inflater.inflate(
R.layout.data_usage_app_title, root, false);
view.setText(label);
return view;
}
/**
* Set {@link android.R.id#title} for a preference view inflated with
* {@link #inflatePreference(LayoutInflater, ViewGroup, View)}.

View File

@@ -17,12 +17,20 @@
package com.android.settings.widget;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import com.android.settings.DataUsageSummary;
import com.android.settings.R;
import com.google.common.base.Preconditions;
@@ -32,14 +40,16 @@ import com.google.common.base.Preconditions;
*/
public class ChartGridView extends View {
// TODO: eventually teach about drawing chart labels
private ChartAxis mHoriz;
private ChartAxis mVert;
private Drawable mPrimary;
private Drawable mSecondary;
private Drawable mBorder;
private int mLabelColor;
private Layout mLayoutStart;
private Layout mLayoutEnd;
public ChartGridView(Context context) {
this(context, null, 0);
@@ -60,7 +70,7 @@ public class ChartGridView extends View {
mPrimary = a.getDrawable(R.styleable.ChartGridView_primaryDrawable);
mSecondary = a.getDrawable(R.styleable.ChartGridView_secondaryDrawable);
mBorder = a.getDrawable(R.styleable.ChartGridView_borderDrawable);
// TODO: eventually read labelColor
mLabelColor = a.getColor(R.styleable.ChartGridView_labelColor, Color.RED);
a.recycle();
}
@@ -70,6 +80,13 @@ public class ChartGridView extends View {
mVert = Preconditions.checkNotNull(vert, "missing vert");
}
void setBounds(long start, long end) {
final Context context = getContext();
mLayoutStart = makeLayout(DataUsageSummary.formatDateRange(context, start, start, true));
mLayoutEnd = makeLayout(DataUsageSummary.formatDateRange(context, end, end, true));
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
final int width = getWidth();
@@ -98,5 +115,38 @@ public class ChartGridView extends View {
mBorder.setBounds(0, 0, width, height);
mBorder.draw(canvas);
final int padding = mLayoutStart.getHeight() / 8;
final Layout start = mLayoutStart;
if (start != null) {
canvas.save();
canvas.translate(0, height + padding);
start.draw(canvas);
canvas.restore();
}
final Layout end = mLayoutEnd;
if (end != null) {
canvas.save();
canvas.translate(width - end.getWidth(), height + padding);
end.draw(canvas);
canvas.restore();
}
}
private Layout makeLayout(CharSequence text) {
final Resources res = getResources();
final TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
paint.density = res.getDisplayMetrics().density;
paint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale);
paint.setColor(mLabelColor);
paint.setTextSize(
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, res.getDisplayMetrics()));
return new StaticLayout(text, paint,
(int) Math.ceil(Layout.getDesiredWidth(text, paint)),
Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true);
}
}

View File

@@ -29,7 +29,6 @@ import android.text.Layout.Alignment;
import android.text.SpannableStringBuilder;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
import android.view.MotionEvent;
import android.view.View;

View File

@@ -343,6 +343,7 @@ public class DataUsageChartView extends ChartView {
*/
public void setVisibleRange(long visibleStart, long visibleEnd) {
mHoriz.setBounds(visibleStart, visibleEnd);
mGrid.setBounds(visibleStart, visibleEnd);
final long validStart = Math.max(visibleStart, getStatsStart());
final long validEnd = Math.min(visibleEnd, getStatsEnd());