More data usage chart iteration, app details.

Moved app details back into single Fragment to support animations and
template tabs.  Show the network in background behind app details
chart series to match designs.

Clamping sweeps at axis boundaries.

Bug: 4813014, 4598460, 4818029
Change-Id: I72c0b21ee1d595e4da31d293ae0dab9e801041f3
This commit is contained in:
Jeff Sharkey
2011-06-23 22:15:54 -07:00
parent 255e71eb53
commit f54f435f1f
38 changed files with 478 additions and 458 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 851 B

After

Width:  |  Height:  |  Size: 857 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 790 B

After

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 711 B

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 833 B

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 766 B

After

Width:  |  Height:  |  Size: 767 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 799 B

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 813 B

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 847 B

After

Width:  |  Height:  |  Size: 850 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 568 B

After

Width:  |  Height:  |  Size: 565 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 526 B

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 563 B

After

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 572 B

After

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 535 B

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 621 B

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 616 B

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -17,6 +17,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_activated="true" android:drawable="@drawable/data_sweep_left_activated" />
<item android:state_activated="false" android:drawable="@drawable/data_sweep_left_default" />
<item android:state_activated="true" android:state_enabled="true" android:drawable="@drawable/data_sweep_left_activated" />
<item android:drawable="@drawable/data_sweep_left_default" />
</selector>

View File

@@ -17,6 +17,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_activated="true" android:drawable="@drawable/data_sweep_limit_activated" />
<item android:state_activated="false" android:drawable="@drawable/data_sweep_limit_default" />
<item android:state_activated="true" android:state_enabled="true" android:drawable="@drawable/data_sweep_limit_activated" />
<item android:drawable="@drawable/data_sweep_limit_default" />
</selector>

View File

@@ -17,6 +17,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_activated="true" android:drawable="@drawable/data_sweep_right_activated" />
<item android:state_activated="false" android:drawable="@drawable/data_sweep_right_default" />
<item android:state_activated="true" android:state_enabled="true" android:drawable="@drawable/data_sweep_right_activated" />
<item android:drawable="@drawable/data_sweep_right_default" />
</selector>

View File

@@ -17,6 +17,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_activated="true" android:drawable="@drawable/data_sweep_warning_activated" />
<item android:state_activated="false" android:drawable="@drawable/data_sweep_warning_default" />
<item android:state_activated="true" android:state_enabled="true" android:drawable="@drawable/data_sweep_warning_activated" />
<item android:drawable="@drawable/data_sweep_warning_default" />
</selector>

View File

@@ -17,6 +17,7 @@
<com.android.settings.widget.DataUsageChartView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
android:id="@+id/chart"
android:layout_width="match_parent"
android:layout_height="220dip"
android:padding="16dip">
@@ -40,11 +41,19 @@
settings:fillColor="#c050ade5"
settings:fillColorSecondary="#88566abc" />
<com.android.settings.widget.ChartNetworkSeriesView
android:id="@+id/detail_series"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="left|bottom"
settings:strokeColor="#d88d3a"
settings:fillColor="#c0ba7f3e"
settings:fillColorSecondary="#0000" />
<com.android.settings.widget.ChartSweepView
android:id="@+id/sweep_left"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
settings:sweepDrawable="@drawable/data_sweep_left"
settings:followAxis="horizontal"
settings:showLabel="false" />
@@ -53,7 +62,6 @@
android:id="@+id/sweep_right"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
settings:sweepDrawable="@drawable/data_sweep_right"
settings:followAxis="horizontal"
settings:showLabel="false" />
@@ -62,7 +70,6 @@
android:id="@+id/sweep_limit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
settings:sweepDrawable="@drawable/data_sweep_limit"
settings:followAxis="vertical"
settings:showLabel="true" />
@@ -71,7 +78,6 @@
android:id="@+id/sweep_warning"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
settings:sweepDrawable="@drawable/data_sweep_warning"
settings:followAxis="vertical"
settings:showLabel="true" />

View File

@@ -14,43 +14,35 @@
limitations under the License.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/app_detail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@+id/chart_container"
android:layout_width="match_parent"
android:layout_height="233dip" />
<TextView
android:id="@android:id/title"
android:id="@+id/app_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dip" />
<TextView
android:id="@android:id/text1"
android:id="@+id/app_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dip" />
<Button
android:id="@+id/data_usage_app_settings"
android:id="@+id/app_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dip"
android:text="@string/data_usage_app_settings" />
<LinearLayout
android:id="@+id/switches"
android:id="@+id/app_switches"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -20,7 +20,7 @@
android:orientation="vertical">
<LinearLayout
android:id="@+id/switches"
android:id="@+id/network_switches"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
@@ -48,4 +48,7 @@
</LinearLayout>
<include layout="@layout/data_usage_chart" />
<include layout="@layout/data_usage_detail" />
</LinearLayout>

View File

@@ -1,309 +0,0 @@
/*
* 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.
*/
package com.android.settings;
import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
import static com.android.settings.DataUsageSummary.getHistoryBounds;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.net.NetworkPolicyManager;
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.text.format.DateUtils;
import android.text.format.Formatter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.settings.widget.DataUsageChartView;
import com.android.settings.widget.DataUsageChartView.DataUsageChartListener;
public class DataUsageAppDetail extends Fragment {
private static final String TAG = "DataUsage";
private static final boolean LOGD = true;
public static final String EXTRA_UID = "uid";
public static final String EXTRA_NETWORK_TEMPLATE = "networkTemplate";
private int mUid;
private NetworkTemplate mTemplate;
private Intent mAppSettingsIntent;
private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict";
private INetworkStatsService mStatsService;
private INetworkPolicyManager mPolicyService;
private CheckBoxPreference mRestrictBackground;
private View mRestrictBackgroundView;
private FrameLayout mChartContainer;
private TextView mTitle;
private TextView mText1;
private Button mAppSettings;
private LinearLayout mSwitches;
private DataUsageChartView mChart;
private NetworkStatsHistory mHistory;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mStatsService = INetworkStatsService.Stub.asInterface(
ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
mPolicyService = INetworkPolicyManager.Stub.asInterface(
ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final Context context = inflater.getContext();
final View view = inflater.inflate(R.layout.data_usage_detail, container, false);
mChartContainer = (FrameLayout) view.findViewById(R.id.chart_container);
mTitle = (TextView) view.findViewById(android.R.id.title);
mText1 = (TextView) view.findViewById(android.R.id.text1);
mAppSettings = (Button) view.findViewById(R.id.data_usage_app_settings);
mSwitches = (LinearLayout) view.findViewById(R.id.switches);
mRestrictBackground = new CheckBoxPreference(context);
mRestrictBackground.setTitle(R.string.data_usage_app_restrict_background);
mRestrictBackground.setSummary(R.string.data_usage_app_restrict_background_summary);
// kick refresh once to force-create views
refreshPreferenceViews();
mSwitches.addView(mRestrictBackgroundView);
mRestrictBackgroundView.setOnClickListener(mRestrictBackgroundListener);
mAppSettings.setOnClickListener(mAppSettingsListener);
mChart = new DataUsageChartView(context);
mChartContainer.addView(mChart);
mChart.setListener(mChartListener);
mChart.setChartColor(Color.parseColor("#d88d3a"), Color.parseColor("#c0ba7f3e"),
Color.parseColor("#88566abc"));
return view;
}
@Override
public void onResume() {
super.onResume();
updateBody();
}
private void updateBody() {
final PackageManager pm = getActivity().getPackageManager();
mUid = getArguments().getInt(EXTRA_UID);
mTemplate = getArguments().getParcelable(EXTRA_NETWORK_TEMPLATE);
mTitle.setText(pm.getNameForUid(mUid));
// enable settings button when package provides it
// TODO: target torwards entire UID instead of just first package
final String[] packageNames = pm.getPackagesForUid(mUid);
if (packageNames != null && packageNames.length > 0) {
mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
mAppSettingsIntent.setPackage(packageNames[0]);
mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
final boolean matchFound = pm.resolveActivity(mAppSettingsIntent, 0) != null;
mAppSettings.setEnabled(matchFound);
} else {
mAppSettingsIntent = null;
mAppSettings.setEnabled(false);
}
try {
// load stats for current uid and template
// TODO: read template from extras
mHistory = mStatsService.getHistoryForUid(mTemplate, mUid, NetworkStats.TAG_NONE);
} catch (RemoteException e) {
// since we can't do much without history, and we don't want to
// leave with half-baked UI, we bail hard.
throw new RuntimeException("problem reading network stats", e);
}
// bind chart to historical stats
mChart.bindNetworkStats(mHistory);
// show entire history known
final long[] bounds = getHistoryBounds(mHistory);
mChart.setVisibleRange(bounds[0], bounds[1] + DateUtils.WEEK_IN_MILLIS, bounds[1]);
updateDetailData();
final Context context = getActivity();
if (NetworkPolicyManager.isUidValidForPolicy(context, mUid)) {
mRestrictBackgroundView.setVisibility(View.VISIBLE);
final int uidPolicy;
try {
uidPolicy = mPolicyService.getUidPolicy(mUid);
} catch (RemoteException e) {
// since we can't do much without policy, we bail hard.
throw new RuntimeException("problem reading network policy", e);
}
// update policy checkbox
final boolean restrictBackground = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
mRestrictBackground.setChecked(restrictBackground);
// kick preference views so they rebind from changes above
refreshPreferenceViews();
} else {
mRestrictBackgroundView.setVisibility(View.GONE);
}
}
private void updateDetailData() {
if (LOGD) Log.d(TAG, "updateDetailData()");
final Context context = mChart.getContext();
final long[] range = mChart.getInspectRange();
final long[] total = mHistory.getTotalData(range[0], range[1], null);
final long totalCombined = total[0] + total[1];
mText1.setText(Formatter.formatFileSize(context, totalCombined));
}
private void setRestrictBackground(boolean restrictBackground) {
if (LOGD) Log.d(TAG, "setRestrictBackground()");
try {
mPolicyService.setUidPolicy(
mUid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
} catch (RemoteException e) {
throw new RuntimeException("unable to save policy", e);
}
mRestrictBackground.setChecked(restrictBackground);
refreshPreferenceViews();
}
/**
* Force rebind of hijacked {@link Preference} views.
*/
private void refreshPreferenceViews() {
mRestrictBackgroundView = mRestrictBackground.getView(mRestrictBackgroundView, mSwitches);
}
private DataUsageChartListener mChartListener = new DataUsageChartListener() {
/** {@inheritDoc} */
public void onInspectRangeChanged() {
if (LOGD) Log.d(TAG, "onInspectRangeChanged()");
updateDetailData();
}
/** {@inheritDoc} */
public void onWarningChanged() {
// ignored
}
/** {@inheritDoc} */
public void onLimitChanged() {
// ignored
}
};
private OnClickListener mAppSettingsListener = new OnClickListener() {
/** {@inheritDoc} */
public void onClick(View v) {
// TODO: target torwards entire UID instead of just first package
startActivity(mAppSettingsIntent);
}
};
private OnClickListener mRestrictBackgroundListener = new OnClickListener() {
/** {@inheritDoc} */
public void onClick(View v) {
final boolean restrictBackground = !mRestrictBackground.isChecked();
if (restrictBackground) {
// enabling restriction; show confirmation dialog which
// eventually calls setRestrictBackground() once user confirms.
ConfirmRestrictFragment.show(DataUsageAppDetail.this);
} else {
setRestrictBackground(false);
}
}
};
/**
* Dialog to request user confirmation before setting
* {@link #POLICY_REJECT_METERED_BACKGROUND}.
*/
public static class ConfirmRestrictFragment extends DialogFragment {
public static void show(DataUsageAppDetail parent) {
final ConfirmRestrictFragment dialog = new ConfirmRestrictFragment();
dialog.setTargetFragment(parent, 0);
dialog.show(parent.getFragmentManager(), TAG_CONFIRM_RESTRICT);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getActivity();
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.data_usage_app_restrict_dialog_title);
builder.setMessage(R.string.data_usage_app_restrict_dialog);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
final DataUsageAppDetail target = (DataUsageAppDetail) getTargetFragment();
if (target != null) {
target.setRestrictBackground(true);
}
}
});
builder.setNegativeButton(android.R.string.cancel, null);
return builder.create();
}
}
}

View File

@@ -21,6 +21,8 @@ import static android.net.ConnectivityManager.TYPE_WIMAX;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicyManager.ACTION_DATA_USAGE_LIMIT;
import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
@@ -29,10 +31,12 @@ import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.animation.LayoutTransition;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -54,7 +58,6 @@ import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
@@ -66,12 +69,14 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
@@ -106,8 +111,6 @@ public class DataUsageSummary extends Fragment {
private static final String TAG = "DataUsage";
private static final boolean LOGD = true;
private static final int TEMPLATE_INVALID = -1;
private static final String TAB_3G = "3g";
private static final String TAB_4G = "4g";
private static final String TAB_MOBILE = "mobile";
@@ -116,6 +119,8 @@ public class DataUsageSummary extends Fragment {
private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
private static final String TAG_CYCLE_EDITOR = "cycleEditor";
private static final String TAG_POLICY_LIMIT = "policyLimit";
private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict";
private static final String TAG_APP_DETAILS = "appDetails";
private static final long KB_IN_BYTES = 1024;
private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
@@ -135,25 +140,41 @@ public class DataUsageSummary extends Fragment {
private ListView mListView;
private DataUsageAdapter mAdapter;
private View mHeader;
private LinearLayout mSwitches;
private ViewGroup mHeader;
private LinearLayout mNetworkSwitches;
private Switch mDataEnabled;
private CheckBox mDisableAtLimit;
private View mDataEnabledView;
private CheckBox mDisableAtLimit;
private View mDisableAtLimitView;
private DataUsageChartView mChart;
private Spinner mCycleSpinner;
private CycleAdapter mCycleAdapter;
private DataUsageChartView mChart;
private View mAppDetail;
private TextView mAppTitle;
private TextView mAppSubtitle;
private Button mAppSettings;
private LinearLayout mAppSwitches;
private CheckBox mAppRestrict;
private View mAppRestrictView;
private boolean mShowWifi = false;
private NetworkTemplate mTemplate = null;
private static final int UID_NONE = -1;
private int mUid = UID_NONE;
private Intent mAppSettingsIntent;
private NetworkPolicyEditor mPolicyEditor;
private NetworkStatsHistory mHistory;
private NetworkStatsHistory mDetailHistory;
private String mIntentTab = null;
@@ -194,30 +215,57 @@ public class DataUsageSummary extends Fragment {
mTabHost.setup();
mTabHost.setOnTabChangedListener(mTabListener);
mHeader = inflater.inflate(R.layout.data_usage_header, mListView, false);
mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false);
mListView.addHeaderView(mHeader, null, false);
{
// bind network switches
mNetworkSwitches = (LinearLayout) mHeader.findViewById(R.id.network_switches);
mDataEnabled = new Switch(inflater.getContext());
mDataEnabledView = inflatePreference(inflater, mSwitches, mDataEnabled);
mDataEnabledView = inflatePreference(inflater, mNetworkSwitches, mDataEnabled);
mDataEnabled.setOnCheckedChangeListener(mDataEnabledListener);
mNetworkSwitches.addView(mDataEnabledView);
mDisableAtLimit = new CheckBox(inflater.getContext());
mDisableAtLimit.setClickable(false);
mDisableAtLimitView = inflatePreference(inflater, mSwitches, mDisableAtLimit);
mDisableAtLimitView = inflatePreference(inflater, mNetworkSwitches, mDisableAtLimit);
mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
mNetworkSwitches.addView(mDisableAtLimitView);
}
mSwitches = (LinearLayout) mHeader.findViewById(R.id.switches);
mSwitches.addView(mDataEnabledView);
mSwitches.addView(mDisableAtLimitView);
// bind cycle dropdown
mCycleSpinner = (Spinner) mHeader.findViewById(R.id.cycles);
mCycleAdapter = new CycleAdapter(context);
mCycleSpinner.setAdapter(mCycleAdapter);
mCycleSpinner.setOnItemSelectedListener(mCycleListener);
mChart = (DataUsageChartView) inflater.inflate(R.layout.data_usage_chart, mListView, false);
mChart = (DataUsageChartView) mHeader.findViewById(R.id.chart);
mChart.setListener(mChartListener);
mListView.addHeaderView(mChart, null, false);
{
// bind app detail controls
mAppDetail = view.findViewById(R.id.app_detail);
mAppTitle = (TextView) view.findViewById(R.id.app_title);
mAppSubtitle = (TextView) view.findViewById(R.id.app_subtitle);
mAppSwitches = (LinearLayout) view.findViewById(R.id.app_switches);
mAppSettings = (Button) view.findViewById(R.id.app_settings);
mAppSettings.setOnClickListener(mAppSettingsListener);
mAppRestrict = new CheckBox(inflater.getContext());
mAppRestrict.setClickable(false);
mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict);
setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
setPreferenceSummary(
mAppRestrictView, R.string.data_usage_app_restrict_background_summary);
mAppRestrictView.setOnClickListener(mAppRestrictListener);
mAppSwitches.addView(mAppRestrictView);
}
// TODO: tweak these transitions
final LayoutTransition transition = new LayoutTransition();
mHeader.setLayoutTransition(transition);
mAdapter = new DataUsageAdapter();
mListView.setOnItemClickListener(mListListener);
@@ -433,6 +481,7 @@ public class DataUsageSummary extends Fragment {
mChart.bindNetworkStats(mHistory);
updatePolicy(true);
updateAppDetail();
// force scroll to top of body
mListView.smoothScrollToPosition(0);
@@ -440,6 +489,84 @@ public class DataUsageSummary extends Fragment {
mBinding = false;
}
private boolean isAppDetailMode() {
return mUid != UID_NONE;
}
/**
* Update UID details panels to match {@link #mUid}, showing or hiding them
* depending on {@link #isAppDetailMode()}.
*/
private void updateAppDetail() {
if (isAppDetailMode()) {
mAppDetail.setVisibility(View.VISIBLE);
} else {
mAppDetail.setVisibility(View.GONE);
// hide detail stats when not in detail mode
mChart.bindDetailNetworkStats(null);
return;
}
// remove warning/limit sweeps while in detail mode
mChart.bindNetworkPolicy(null);
final PackageManager pm = getActivity().getPackageManager();
mAppTitle.setText(pm.getNameForUid(mUid));
// enable settings button when package provides it
// TODO: target torwards entire UID instead of just first package
final String[] packageNames = pm.getPackagesForUid(mUid);
if (packageNames != null && packageNames.length > 0) {
mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
mAppSettingsIntent.setPackage(packageNames[0]);
mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
final boolean matchFound = pm.resolveActivity(mAppSettingsIntent, 0) != null;
mAppSettings.setEnabled(matchFound);
} else {
mAppSettingsIntent = null;
mAppSettings.setEnabled(false);
}
try {
// load stats for current uid and template
// TODO: read template from extras
mDetailHistory = mStatsService.getHistoryForUid(mTemplate, mUid, NetworkStats.TAG_NONE);
} catch (RemoteException e) {
// since we can't do much without history, and we don't want to
// leave with half-baked UI, we bail hard.
throw new RuntimeException("problem reading network stats", e);
}
// bind chart to historical stats
mChart.bindDetailNetworkStats(mDetailHistory);
updateDetailData();
final Context context = getActivity();
if (NetworkPolicyManager.isUidValidForPolicy(context, mUid)) {
mAppRestrictView.setVisibility(View.VISIBLE);
final int uidPolicy;
try {
uidPolicy = mPolicyService.getUidPolicy(mUid);
} catch (RemoteException e) {
// since we can't do much without policy, we bail hard.
throw new RuntimeException("problem reading network policy", e);
}
// update policy checkbox
final boolean restrictBackground = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
mAppRestrict.setChecked(restrictBackground);
} else {
mAppRestrictView.setVisibility(View.GONE);
}
}
private void setPolicyCycleDay(int cycleDay) {
if (LOGD) Log.d(TAG, "setPolicyCycleDay()");
mPolicyEditor.setPolicyCycleDay(mTemplate, cycleDay);
@@ -458,11 +585,30 @@ public class DataUsageSummary extends Fragment {
updatePolicy(false);
}
private void setAppRestrictBackground(boolean restrictBackground) {
if (LOGD) Log.d(TAG, "setRestrictBackground()");
try {
mPolicyService.setUidPolicy(
mUid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
} catch (RemoteException e) {
throw new RuntimeException("unable to save policy", e);
}
mAppRestrict.setChecked(restrictBackground);
}
/**
* Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
* current {@link #mTemplate}.
*/
private void updatePolicy(boolean refreshCycle) {
if (isAppDetailMode()) {
mNetworkSwitches.setVisibility(View.GONE);
// we fall through to update cycle list for detail mode
} else {
mNetworkSwitches.setVisibility(View.VISIBLE);
}
final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate);
// reflect policy limit in checkbox
@@ -572,18 +718,34 @@ public class DataUsageSummary extends Fragment {
}
};
private View.OnClickListener mAppRestrictListener = new View.OnClickListener() {
/** {@inheritDoc} */
public void onClick(View v) {
final boolean restrictBackground = !mAppRestrict.isChecked();
if (restrictBackground) {
// enabling restriction; show confirmation dialog which
// eventually calls setRestrictBackground() once user confirms.
ConfirmRestrictFragment.show(DataUsageSummary.this);
} else {
setAppRestrictBackground(false);
}
}
};
private OnClickListener mAppSettingsListener = new OnClickListener() {
/** {@inheritDoc} */
public void onClick(View v) {
// TODO: target torwards entire UID instead of just first package
startActivity(mAppSettingsIntent);
}
};
private OnItemClickListener mListListener = new OnItemClickListener() {
/** {@inheritDoc} */
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
final Bundle args = new Bundle();
args.putParcelable(DataUsageAppDetail.EXTRA_NETWORK_TEMPLATE, mTemplate);
args.putInt(DataUsageAppDetail.EXTRA_UID, app.uid);
final PreferenceActivity activity = (PreferenceActivity) getActivity();
activity.startPreferencePanel(DataUsageAppDetail.class.getName(), args,
R.string.data_usage_summary_title, null, null, 0);
AppDetailsFragment.show(DataUsageSummary.this, app.uid);
}
};
@@ -621,12 +783,30 @@ public class DataUsageSummary extends Fragment {
};
/**
* Update {@link #mAdapter} with sorted list of applications data usage,
* based on current inspection from {@link #mChart}.
* Update details based on {@link #mChart} inspection range depending on
* current mode. In network mode, updates {@link #mAdapter} with sorted list
* of applications data usage, and when {@link #isAppDetailMode()} update
* app details.
*/
private void updateDetailData() {
if (LOGD) Log.d(TAG, "updateDetailData()");
if (isAppDetailMode()) {
if (mDetailHistory != null) {
final Context context = mChart.getContext();
final long[] range = mChart.getInspectRange();
final long[] total = mDetailHistory.getTotalData(range[0], range[1], null);
final long totalCombined = total[0] + total[1];
mAppSubtitle.setText(Formatter.formatFileSize(context, totalCombined));
}
// clear any existing app list details
mAdapter.bindStats(null);
return;
}
// otherwise kick off task to update list
new AsyncTask<Void, Void, NetworkStats>() {
@Override
protected NetworkStats doInBackground(Void... params) {
@@ -753,9 +933,13 @@ public class DataUsageSummary extends Fragment {
public static class DataUsageAdapter extends BaseAdapter {
private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
/**
* Bind the given {@link NetworkStats}, or {@code null} to clear list.
*/
public void bindStats(NetworkStats stats) {
mItems.clear();
if (stats != null) {
for (int i = 0; i < stats.size; i++) {
final long total = stats.rx[i] + stats.tx[i];
final AppUsageItem item = new AppUsageItem();
@@ -763,6 +947,7 @@ public class DataUsageSummary extends Fragment {
item.total = total;
mItems.add(item);
}
}
Collections.sort(mItems);
notifyDataSetChanged();
@@ -805,6 +990,44 @@ public class DataUsageSummary extends Fragment {
}
/**
* Empty {@link Fragment} that controls display of UID details in
* {@link DataUsageSummary}.
*/
public static class AppDetailsFragment extends Fragment {
public static final String EXTRA_UID = "uid";
public static void show(DataUsageSummary parent, int uid) {
final Bundle args = new Bundle();
args.putInt(EXTRA_UID, uid);
final AppDetailsFragment fragment = new AppDetailsFragment();
fragment.setArguments(args);
fragment.setTargetFragment(parent, 0);
final FragmentTransaction ft = parent.getFragmentManager().beginTransaction();
ft.add(fragment, TAG_APP_DETAILS);
ft.addToBackStack(TAG_APP_DETAILS);
ft.commit();
}
@Override
public void onStart() {
super.onStart();
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
target.mUid = getArguments().getInt(EXTRA_UID);
target.updateBody();
}
@Override
public void onStop() {
super.onStop();
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
target.mUid = UID_NONE;
target.updateBody();
}
}
/**
* Dialog to request user confirmation before setting
* {@link NetworkPolicy#limitBytes}.
@@ -975,12 +1198,45 @@ public class DataUsageSummary extends Fragment {
}
}
/**
* Dialog to request user confirmation before setting
* {@link #POLICY_REJECT_METERED_BACKGROUND}.
*/
public static class ConfirmRestrictFragment extends DialogFragment {
public static void show(DataUsageSummary parent) {
final ConfirmRestrictFragment dialog = new ConfirmRestrictFragment();
dialog.setTargetFragment(parent, 0);
dialog.show(parent.getFragmentManager(), TAG_CONFIRM_RESTRICT);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getActivity();
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.data_usage_app_restrict_dialog_title);
builder.setMessage(R.string.data_usage_app_restrict_dialog);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
if (target != null) {
target.setAppRestrictBackground(true);
}
}
});
builder.setNegativeButton(android.R.string.cancel, null);
return builder.create();
}
}
/**
* Compute default tab that should be selected, based on
* {@link NetworkPolicyManager#EXTRA_NETWORK_TEMPLATE} extra.
*/
private static String computeTabFromIntent(Intent intent) {
final int networkTemplate = intent.getIntExtra(EXTRA_NETWORK_TEMPLATE, TEMPLATE_INVALID);
final int networkTemplate = intent.getIntExtra(EXTRA_NETWORK_TEMPLATE, MATCH_MOBILE_ALL);
switch (networkTemplate) {
case MATCH_MOBILE_3G_LOWER:
return TAB_3G;
@@ -1083,4 +1339,14 @@ public class DataUsageSummary extends Fragment {
title.setText(resId);
}
/**
* Set {@link android.R.id#summary} for a preference view inflated with
* {@link #inflatePreference(LayoutInflater, ViewGroup, View)}.
*/
private static void setPreferenceSummary(View parent, int resId) {
final TextView summary = (TextView) parent.findViewById(android.R.id.summary);
summary.setVisibility(View.VISIBLE);
summary.setText(resId);
}
}

View File

@@ -16,6 +16,8 @@
package com.android.settings.net;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.WARNING_DISABLED;
import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
@@ -51,6 +53,14 @@ public class NetworkPolicyEditor {
final NetworkPolicy[] policies = mPolicyService.getNetworkPolicies();
mPolicies.clear();
for (NetworkPolicy policy : policies) {
// TODO: find better place to clamp these
if (policy.limitBytes < -1) {
policy.limitBytes = LIMIT_DISABLED;
}
if (policy.warningBytes < -1) {
policy.warningBytes = WARNING_DISABLED;
}
mPolicies.add(policy);
}
} catch (RemoteException e) {

View File

@@ -75,6 +75,7 @@ public class ChartNetworkSeriesView extends View {
R.styleable.ChartNetworkSeriesView_fillColorSecondary, Color.RED);
setChartColor(stroke, fill, fillSecondary);
setWillNotDraw(false);
a.recycle();
@@ -110,8 +111,13 @@ public class ChartNetworkSeriesView extends View {
mPathStroke.reset();
mPathFill.reset();
invalidate();
}
/**
* Set the range to paint with {@link #mPaintFill}, leaving the remaining
* area to be painted with {@link #mPaintFillSecondary}.
*/
public void setPrimaryRange(long left, long right) {
mPrimaryLeft = left;
mPrimaryRight = right;
@@ -190,18 +196,21 @@ public class ChartNetworkSeriesView extends View {
protected void onDraw(Canvas canvas) {
int save;
final float primaryLeftPoint = mHoriz.convertToPoint(mPrimaryLeft);
final float primaryRightPoint = mHoriz.convertToPoint(mPrimaryRight);
save = canvas.save();
canvas.clipRect(0, 0, mPrimaryLeft, getHeight());
canvas.clipRect(0, 0, primaryLeftPoint, getHeight());
canvas.drawPath(mPathFill, mPaintFillSecondary);
canvas.restoreToCount(save);
save = canvas.save();
canvas.clipRect(mPrimaryRight, 0, getWidth(), getHeight());
canvas.clipRect(primaryRightPoint, 0, getWidth(), getHeight());
canvas.drawPath(mPathFill, mPaintFillSecondary);
canvas.restoreToCount(save);
save = canvas.save();
canvas.clipRect(mPrimaryLeft, 0, mPrimaryRight, getHeight());
canvas.clipRect(primaryLeftPoint, 0, primaryRightPoint, getHeight());
canvas.drawPath(mPathFill, mPaintFill);
canvas.drawPath(mPathStroke, mPaintStroke);
canvas.restoreToCount(save);

View File

@@ -39,6 +39,8 @@ public class ChartSweepView extends FrameLayout {
// TODO: paint label when requested
private Drawable mSweep;
private Rect mSweepMargins = new Rect();
private int mFollowAxis;
private boolean mShowLabel;
@@ -88,8 +90,28 @@ public class ChartSweepView extends FrameLayout {
return mFollowAxis;
}
public void getExtraMargins(Rect rect) {
mSweep.getPadding(rect);
/**
* Return margins of {@link #setSweepDrawable(Drawable)}, indicating how the
* sweep should be displayed around a content region.
*/
public Rect getSweepMargins() {
return mSweepMargins;
}
/**
* Return the number of pixels that the "target" area is inset from the
* {@link View} edge, along the current {@link #setFollowAxis(int)}.
*/
public float getTargetInset() {
if (mFollowAxis == VERTICAL) {
final float targetHeight = mSweep.getIntrinsicHeight() - mSweepMargins.top
- mSweepMargins.bottom;
return mSweepMargins.top + (targetHeight / 2);
} else {
final float targetWidth = mSweep.getIntrinsicWidth() - mSweepMargins.left
- mSweepMargins.right;
return mSweepMargins.left + (targetWidth / 2);
}
}
public void addOnSweepListener(OnSweepListener listener) {
@@ -115,6 +137,7 @@ public class ChartSweepView extends FrameLayout {
}
sweep.setVisible(getVisibility() == VISIBLE, false);
mSweep = sweep;
sweep.getPadding(mSweepMargins);
} else {
mSweep = null;
}
@@ -175,33 +198,51 @@ public class ChartSweepView extends FrameLayout {
final View parent = (View) getParent();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
// only start tracking when in sweet spot
final boolean accept;
if (mFollowAxis == VERTICAL) {
accept = event.getX() > getWidth() - (mSweepMargins.right * 2);
} else {
accept = event.getY() > getHeight() - (mSweepMargins.bottom * 2);
}
if (accept) {
mTracking = event.copy();
return true;
} else {
return false;
}
}
case MotionEvent.ACTION_MOVE: {
getParent().requestDisallowInterceptTouchEvent(true);
final Rect sweepMargins = mSweepMargins;
// content area of parent
final Rect parentContent = new Rect(parent.getPaddingLeft(), parent.getPaddingTop(),
parent.getWidth() - parent.getPaddingRight(),
parent.getHeight() - parent.getPaddingBottom());
if (mFollowAxis == VERTICAL) {
final float chartHeight = parent.getHeight() - parent.getPaddingTop()
- parent.getPaddingBottom();
final float translationY = MathUtils.constrain(
event.getRawY() - mTracking.getRawY(), -getTop(),
chartHeight - getTop());
setTranslationY(translationY);
final float point = (getTop() + getTranslationY() + (getHeight() / 2))
- parent.getPaddingTop();
mValue = mAxis.convertToValue(point);
final float currentTargetY = getTop() + getTargetInset();
final float requestedTargetY = currentTargetY
+ (event.getRawY() - mTracking.getRawY());
final float clampedTargetY = MathUtils.constrain(
requestedTargetY, parentContent.top, parentContent.bottom);
setTranslationY(clampedTargetY - currentTargetY);
mValue = mAxis.convertToValue(clampedTargetY - parentContent.top);
dispatchOnSweep(false);
} else {
final float chartWidth = parent.getWidth() - parent.getPaddingLeft()
- parent.getPaddingRight();
final float translationX = MathUtils.constrain(
event.getRawX() - mTracking.getRawX(), -getLeft(),
chartWidth - getLeft());
setTranslationX(translationX);
final float point = (getLeft() + getTranslationX() + (getWidth() / 2))
- parent.getPaddingLeft();
mValue = mAxis.convertToValue(point);
final float currentTargetX = getLeft() + getTargetInset();
final float requestedTargetX = currentTargetX
+ (event.getRawX() - mTracking.getRawX());
final float clampedTargetX = MathUtils.constrain(
requestedTargetX, parentContent.left, parentContent.right);
setTranslationX(clampedTargetX - currentTargetX);
mValue = mAxis.convertToValue(clampedTargetX - parentContent.left);
dispatchOnSweep(false);
}
return true;

View File

@@ -36,6 +36,8 @@ public class ChartView extends FrameLayout {
// TODO: extend something that supports two-dimensional scrolling
private static final int SWEEP_GRAVITY = Gravity.TOP | Gravity.LEFT;
ChartAxis mHoriz;
ChartAxis mVert;
@@ -74,7 +76,6 @@ public class ChartView extends FrameLayout {
final Rect parentRect = new Rect();
final Rect childRect = new Rect();
final Rect extraMargins = new Rect();
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
@@ -91,21 +92,23 @@ public class ChartView extends FrameLayout {
} else if (child instanceof ChartSweepView) {
// sweep is always placed along specific dimension
final ChartSweepView sweep = (ChartSweepView) child;
final Rect sweepMargins = sweep.getSweepMargins();
final float point = sweep.getPoint();
sweep.getExtraMargins(extraMargins);
if (sweep.getFollowAxis() == ChartSweepView.HORIZONTAL) {
parentRect.left = parentRect.right = (int) point + getPaddingLeft();
parentRect.top -= extraMargins.top;
parentRect.bottom += extraMargins.bottom;
Gravity.apply(params.gravity, child.getMeasuredWidth(), parentRect.height(),
parentRect.left = parentRect.right =
(int) (sweep.getPoint() - sweep.getTargetInset()) + getPaddingLeft();
parentRect.top -= sweepMargins.top;
parentRect.bottom += sweepMargins.bottom;
Gravity.apply(SWEEP_GRAVITY, child.getMeasuredWidth(), parentRect.height(),
parentRect, childRect);
} else {
parentRect.top = parentRect.bottom = (int) point + getPaddingTop();
parentRect.left -= extraMargins.left;
parentRect.right += extraMargins.right;
Gravity.apply(params.gravity, parentRect.width(), child.getMeasuredHeight(),
parentRect.top = parentRect.bottom =
(int) (sweep.getPoint() - sweep.getTargetInset()) + getPaddingTop();
parentRect.left -= sweepMargins.left;
parentRect.right += sweepMargins.right;
Gravity.apply(SWEEP_GRAVITY, parentRect.width(), child.getMeasuredHeight(),
parentRect, childRect);
}
}

View File

@@ -38,10 +38,10 @@ public class DataUsageChartView extends ChartView {
private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
// TODO: enforce that sweeps cant cross each other
// TODO: limit sweeps at graph boundaries
private ChartGridView mGrid;
private ChartNetworkSeriesView mSeries;
private ChartNetworkSeriesView mDetailSeries;
private ChartSweepView mSweepLeft;
private ChartSweepView mSweepRight;
@@ -75,6 +75,8 @@ public class DataUsageChartView extends ChartView {
mGrid = (ChartGridView) findViewById(R.id.grid);
mSeries = (ChartNetworkSeriesView) findViewById(R.id.series);
mDetailSeries = (ChartNetworkSeriesView) findViewById(R.id.detail_series);
mDetailSeries.setVisibility(View.GONE);
mSweepLeft = (ChartSweepView) findViewById(R.id.sweep_left);
mSweepRight = (ChartSweepView) findViewById(R.id.sweep_right);
@@ -89,6 +91,7 @@ public class DataUsageChartView extends ChartView {
// tell everyone about our axis
mGrid.init(mHoriz, mVert);
mSeries.init(mHoriz, mVert);
mDetailSeries.init(mHoriz, mVert);
mSweepLeft.init(mHoriz);
mSweepRight.init(mHoriz);
mSweepWarning.init(mVert);
@@ -97,27 +100,21 @@ public class DataUsageChartView extends ChartView {
setActivated(false);
}
@Override
public void setActivated(boolean activated) {
super.setActivated(activated);
mSweepLeft.setEnabled(activated);
mSweepRight.setEnabled(activated);
mSweepWarning.setEnabled(activated);
mSweepLimit.setEnabled(activated);
}
@Deprecated
public void setChartColor(int stroke, int fill, int disabled) {
mSeries.setChartColor(stroke, fill, disabled);
}
public void setListener(DataUsageChartListener listener) {
mListener = listener;
}
public void bindNetworkStats(NetworkStatsHistory stats) {
mSeries.bindNetworkStats(stats);
updatePrimaryRange();
requestLayout();
}
public void bindDetailNetworkStats(NetworkStatsHistory stats) {
mDetailSeries.bindNetworkStats(stats);
mDetailSeries.setVisibility(stats != null ? View.VISIBLE : View.GONE);
updatePrimaryRange();
requestLayout();
}
public void bindNetworkPolicy(NetworkPolicy policy) {
@@ -146,22 +143,11 @@ public class DataUsageChartView extends ChartView {
}
requestLayout();
// TODO: eventually remove this; was to work around lack of sweep clamping
if (policy.limitBytes < -1 || policy.limitBytes > 5 * GB_IN_BYTES) {
policy.limitBytes = 5 * GB_IN_BYTES;
mLimitListener.onSweep(mSweepLimit, true);
}
if (policy.warningBytes < -1 || policy.warningBytes > 5 * GB_IN_BYTES) {
policy.warningBytes = 4 * GB_IN_BYTES;
mWarningListener.onSweep(mSweepWarning, true);
}
}
private OnSweepListener mSweepListener = new OnSweepListener() {
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
mSeries.setPrimaryRange(mSweepLeft.getValue(), mSweepRight.getValue());
updatePrimaryRange();
// update detail list only when done sweeping
if (sweepDone && mListener != null) {
@@ -236,13 +222,26 @@ public class DataUsageChartView extends ChartView {
mSweepLeft.setValue(sweepMin);
mSweepRight.setValue(sweepMax);
mSeries.setPrimaryRange(sweepMin, sweepMax);
updatePrimaryRange();
requestLayout();
mSeries.generatePath();
mSeries.invalidate();
}
private void updatePrimaryRange() {
final long left = mSweepLeft.getValue();
final long right = mSweepRight.getValue();
// prefer showing primary range on detail series, when available
if (mDetailSeries.getVisibility() == View.VISIBLE) {
mDetailSeries.setPrimaryRange(left, right);
mSeries.setPrimaryRange(0, 0);
} else {
mSeries.setPrimaryRange(left, right);
}
}
public static class TimeAxis implements ChartAxis {
private static final long TICK_INTERVAL = DateUtils.DAY_IN_MILLIS * 7;