Merge "Data usage UI fixes; sweeps, combined history."
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<com.android.settings.widget.DataUsageChartView
|
<com.android.settings.widget.ChartDataUsageView
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
|
xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
|
||||||
android:id="@+id/chart"
|
android:id="@+id/chart"
|
||||||
@@ -55,28 +55,6 @@
|
|||||||
settings:fillColor="#c0ba7f3e"
|
settings:fillColor="#c0ba7f3e"
|
||||||
settings:fillColorSecondary="#60ba7f3e" />
|
settings:fillColorSecondary="#60ba7f3e" />
|
||||||
|
|
||||||
<com.android.settings.widget.ChartSweepView
|
|
||||||
android:id="@+id/sweep_warning"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
settings:sweepDrawable="@drawable/data_sweep_warning"
|
|
||||||
settings:followAxis="vertical"
|
|
||||||
settings:neighborMargin="40dip"
|
|
||||||
settings:labelSize="60dip"
|
|
||||||
settings:labelTemplate="@string/data_usage_sweep_warning"
|
|
||||||
settings:labelColor="#f7931d" />
|
|
||||||
|
|
||||||
<com.android.settings.widget.ChartSweepView
|
|
||||||
android:id="@+id/sweep_limit"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
settings:sweepDrawable="@drawable/data_sweep_limit"
|
|
||||||
settings:followAxis="vertical"
|
|
||||||
settings:neighborMargin="40dip"
|
|
||||||
settings:labelSize="60dip"
|
|
||||||
settings:labelTemplate="@string/data_usage_sweep_limit"
|
|
||||||
settings:labelColor="#c01a2c" />
|
|
||||||
|
|
||||||
<com.android.settings.widget.ChartSweepView
|
<com.android.settings.widget.ChartSweepView
|
||||||
android:id="@+id/sweep_left"
|
android:id="@+id/sweep_left"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -93,4 +71,26 @@
|
|||||||
settings:followAxis="horizontal"
|
settings:followAxis="horizontal"
|
||||||
settings:neighborMargin="5dip" />
|
settings:neighborMargin="5dip" />
|
||||||
|
|
||||||
</com.android.settings.widget.DataUsageChartView>
|
<com.android.settings.widget.ChartSweepView
|
||||||
|
android:id="@+id/sweep_warning"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
settings:sweepDrawable="@drawable/data_sweep_warning"
|
||||||
|
settings:followAxis="vertical"
|
||||||
|
settings:neighborMargin="5dip"
|
||||||
|
settings:labelSize="60dip"
|
||||||
|
settings:labelTemplate="@string/data_usage_sweep_warning"
|
||||||
|
settings:labelColor="#f7931d" />
|
||||||
|
|
||||||
|
<com.android.settings.widget.ChartSweepView
|
||||||
|
android:id="@+id/sweep_limit"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
settings:sweepDrawable="@drawable/data_sweep_limit"
|
||||||
|
settings:followAxis="vertical"
|
||||||
|
settings:neighborMargin="5dip"
|
||||||
|
settings:labelSize="60dip"
|
||||||
|
settings:labelTemplate="@string/data_usage_sweep_limit"
|
||||||
|
settings:labelColor="#c01a2c" />
|
||||||
|
|
||||||
|
</com.android.settings.widget.ChartDataUsageView>
|
||||||
|
|||||||
@@ -122,12 +122,13 @@ import android.widget.TextView;
|
|||||||
import com.android.internal.telephony.Phone;
|
import com.android.internal.telephony.Phone;
|
||||||
import com.android.settings.net.NetworkPolicyEditor;
|
import com.android.settings.net.NetworkPolicyEditor;
|
||||||
import com.android.settings.net.SummaryForAllUidLoader;
|
import com.android.settings.net.SummaryForAllUidLoader;
|
||||||
import com.android.settings.widget.DataUsageChartView;
|
import com.android.settings.widget.ChartDataUsageView;
|
||||||
import com.android.settings.widget.DataUsageChartView.DataUsageChartListener;
|
import com.android.settings.widget.ChartDataUsageView.DataUsageChartListener;
|
||||||
import com.android.settings.widget.PieChartView;
|
import com.android.settings.widget.PieChartView;
|
||||||
import com.google.android.collect.Lists;
|
import com.google.android.collect.Lists;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
@@ -195,7 +196,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
private Spinner mCycleSpinner;
|
private Spinner mCycleSpinner;
|
||||||
private CycleAdapter mCycleAdapter;
|
private CycleAdapter mCycleAdapter;
|
||||||
|
|
||||||
private DataUsageChartView mChart;
|
private ChartDataUsageView mChart;
|
||||||
private TextView mUsageSummary;
|
private TextView mUsageSummary;
|
||||||
private TextView mEmpty;
|
private TextView mEmpty;
|
||||||
|
|
||||||
@@ -216,8 +217,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
|
|
||||||
private NetworkTemplate mTemplate = null;
|
private NetworkTemplate mTemplate = null;
|
||||||
|
|
||||||
private static final int UID_NONE = -1;
|
private int[] mAppDetailUids = null;
|
||||||
private int mUid = UID_NONE;
|
|
||||||
|
|
||||||
private Intent mAppSettingsIntent;
|
private Intent mAppSettingsIntent;
|
||||||
|
|
||||||
@@ -307,7 +307,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
mCycleSpinner.setAdapter(mCycleAdapter);
|
mCycleSpinner.setAdapter(mCycleAdapter);
|
||||||
mCycleSpinner.setOnItemSelectedListener(mCycleListener);
|
mCycleSpinner.setOnItemSelectedListener(mCycleListener);
|
||||||
|
|
||||||
mChart = (DataUsageChartView) mHeader.findViewById(R.id.chart);
|
mChart = (ChartDataUsageView) mHeader.findViewById(R.id.chart);
|
||||||
mChart.setListener(mChartListener);
|
mChart.setListener(mChartListener);
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -611,8 +611,9 @@ public class DataUsageSummary extends Fragment {
|
|||||||
mTemplate = buildTemplateMobile4g(getActiveSubscriberId(context));
|
mTemplate = buildTemplateMobile4g(getActiveSubscriberId(context));
|
||||||
|
|
||||||
} else if (TAB_WIFI.equals(currentTab)) {
|
} else if (TAB_WIFI.equals(currentTab)) {
|
||||||
|
// wifi doesn't have any controls
|
||||||
mDataEnabledView.setVisibility(View.GONE);
|
mDataEnabledView.setVisibility(View.GONE);
|
||||||
setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_wifi_limit);
|
mDisableAtLimitView.setVisibility(View.GONE);
|
||||||
mTemplate = buildTemplateWifi();
|
mTemplate = buildTemplateWifi();
|
||||||
|
|
||||||
} else if (TAB_ETHERNET.equals(currentTab)) {
|
} else if (TAB_ETHERNET.equals(currentTab)) {
|
||||||
@@ -649,12 +650,16 @@ public class DataUsageSummary extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAppDetailMode() {
|
private boolean isAppDetailMode() {
|
||||||
return mUid != UID_NONE;
|
return mAppDetailUids != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getAppDetailPrimaryUid() {
|
||||||
|
return mAppDetailUids[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update UID details panels to match {@link #mUid}, showing or hiding them
|
* Update UID details panels to match {@link #mAppDetailUids}, showing or
|
||||||
* depending on {@link #isAppDetailMode()}.
|
* hiding them depending on {@link #isAppDetailMode()}.
|
||||||
*/
|
*/
|
||||||
private void updateAppDetail() {
|
private void updateAppDetail() {
|
||||||
final Context context = getActivity();
|
final Context context = getActivity();
|
||||||
@@ -681,7 +686,8 @@ public class DataUsageSummary extends Fragment {
|
|||||||
mChart.bindNetworkPolicy(null);
|
mChart.bindNetworkPolicy(null);
|
||||||
|
|
||||||
// show icon and all labels appearing under this app
|
// show icon and all labels appearing under this app
|
||||||
final UidDetail detail = resolveDetailForUid(context, mUid);
|
final int primaryUid = getAppDetailPrimaryUid();
|
||||||
|
final UidDetail detail = resolveDetailForUid(context, primaryUid);
|
||||||
mAppIcon.setImageDrawable(detail.icon);
|
mAppIcon.setImageDrawable(detail.icon);
|
||||||
|
|
||||||
mAppTitles.removeAllViews();
|
mAppTitles.removeAllViews();
|
||||||
@@ -695,7 +701,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
|
|
||||||
// enable settings button when package provides it
|
// enable settings button when package provides it
|
||||||
// TODO: target torwards entire UID instead of just first package
|
// TODO: target torwards entire UID instead of just first package
|
||||||
final String[] packageNames = pm.getPackagesForUid(mUid);
|
final String[] packageNames = pm.getPackagesForUid(primaryUid);
|
||||||
if (packageNames != null && packageNames.length > 0) {
|
if (packageNames != null && packageNames.length > 0) {
|
||||||
mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
|
mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
|
||||||
mAppSettingsIntent.setPackage(packageNames[0]);
|
mAppSettingsIntent.setPackage(packageNames[0]);
|
||||||
@@ -709,12 +715,40 @@ public class DataUsageSummary extends Fragment {
|
|||||||
mAppSettings.setEnabled(false);
|
mAppSettings.setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateDetailHistory();
|
||||||
|
updateDetailData();
|
||||||
|
|
||||||
|
if (NetworkPolicyManager.isUidValidForPolicy(context, primaryUid)
|
||||||
|
&& !getRestrictBackground() && isBandwidthControlEnabled()) {
|
||||||
|
setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
|
||||||
|
setPreferenceSummary(mAppRestrictView,
|
||||||
|
getString(R.string.data_usage_app_restrict_background_summary,
|
||||||
|
buildLimitedNetworksList()));
|
||||||
|
|
||||||
|
mAppRestrictView.setVisibility(View.VISIBLE);
|
||||||
|
mAppRestrict.setChecked(getAppRestrictBackground());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
mAppRestrictView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update {@link #mDetailHistory} and related values based on
|
||||||
|
* {@link #mAppDetailUids}.
|
||||||
|
*/
|
||||||
|
private void updateDetailHistory() {
|
||||||
try {
|
try {
|
||||||
|
mDetailHistoryDefault = null;
|
||||||
|
mDetailHistoryForeground = null;
|
||||||
|
|
||||||
// load stats for current uid and template
|
// load stats for current uid and template
|
||||||
mDetailHistoryDefault = mStatsService.getHistoryForUid(
|
for (int uid : mAppDetailUids) {
|
||||||
mTemplate, mUid, SET_DEFAULT, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
|
mDetailHistoryDefault = collectHistoryForUid(
|
||||||
mDetailHistoryForeground = mStatsService.getHistoryForUid(
|
uid, SET_DEFAULT, mDetailHistoryDefault);
|
||||||
mTemplate, mUid, SET_FOREGROUND, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
|
mDetailHistoryForeground = collectHistoryForUid(
|
||||||
|
uid, SET_FOREGROUND, mDetailHistoryForeground);
|
||||||
|
}
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
// since we can't do much without history, and we don't want to
|
// since we can't do much without history, and we don't want to
|
||||||
// leave with half-baked UI, we bail hard.
|
// leave with half-baked UI, we bail hard.
|
||||||
@@ -727,23 +761,24 @@ public class DataUsageSummary extends Fragment {
|
|||||||
|
|
||||||
// bind chart to historical stats
|
// bind chart to historical stats
|
||||||
mChart.bindDetailNetworkStats(mDetailHistory);
|
mChart.bindDetailNetworkStats(mDetailHistory);
|
||||||
|
|
||||||
updateDetailData();
|
|
||||||
|
|
||||||
if (NetworkPolicyManager.isUidValidForPolicy(context, mUid) && !getRestrictBackground()
|
|
||||||
&& isBandwidthControlEnabled()) {
|
|
||||||
setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
|
|
||||||
setPreferenceSummary(mAppRestrictView,
|
|
||||||
getString(R.string.data_usage_app_restrict_background_summary,
|
|
||||||
buildLimitedNetworksList()));
|
|
||||||
|
|
||||||
mAppRestrictView.setVisibility(View.VISIBLE);
|
|
||||||
mAppRestrict.setChecked(getAppRestrictBackground());
|
|
||||||
|
|
||||||
} else {
|
|
||||||
mAppRestrictView.setVisibility(View.GONE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect {@link NetworkStatsHistory} for the requested UID, combining with
|
||||||
|
* an existing {@link NetworkStatsHistory} if provided.
|
||||||
|
*/
|
||||||
|
private NetworkStatsHistory collectHistoryForUid(
|
||||||
|
int uid, int set, NetworkStatsHistory existing)
|
||||||
|
throws RemoteException {
|
||||||
|
final NetworkStatsHistory history = mStatsService.getHistoryForUid(
|
||||||
|
mTemplate, uid, set, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
|
||||||
|
|
||||||
|
if (existing != null) {
|
||||||
|
existing.recordEntireHistory(history);
|
||||||
|
return existing;
|
||||||
|
} else {
|
||||||
|
return history;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPolicyCycleDay(int cycleDay) {
|
private void setPolicyCycleDay(int cycleDay) {
|
||||||
@@ -764,8 +799,8 @@ public class DataUsageSummary extends Fragment {
|
|||||||
updatePolicy(false);
|
updatePolicy(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isNetworkPolicyModifiable() {
|
private boolean isNetworkPolicyModifiable(NetworkPolicy policy) {
|
||||||
return isBandwidthControlEnabled() && mDataEnabled.isChecked();
|
return policy != null && isBandwidthControlEnabled() && mDataEnabled.isChecked();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isBandwidthControlEnabled() {
|
private boolean isBandwidthControlEnabled() {
|
||||||
@@ -810,9 +845,10 @@ public class DataUsageSummary extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean getAppRestrictBackground() {
|
private boolean getAppRestrictBackground() {
|
||||||
|
final int primaryUid = getAppDetailPrimaryUid();
|
||||||
final int uidPolicy;
|
final int uidPolicy;
|
||||||
try {
|
try {
|
||||||
uidPolicy = mPolicyService.getUidPolicy(mUid);
|
uidPolicy = mPolicyService.getUidPolicy(primaryUid);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
// since we can't do much without policy, we bail hard.
|
// since we can't do much without policy, we bail hard.
|
||||||
throw new RuntimeException("problem reading network policy", e);
|
throw new RuntimeException("problem reading network policy", e);
|
||||||
@@ -823,9 +859,10 @@ public class DataUsageSummary extends Fragment {
|
|||||||
|
|
||||||
private void setAppRestrictBackground(boolean restrictBackground) {
|
private void setAppRestrictBackground(boolean restrictBackground) {
|
||||||
if (LOGD) Log.d(TAG, "setAppRestrictBackground()");
|
if (LOGD) Log.d(TAG, "setAppRestrictBackground()");
|
||||||
|
final int primaryUid = getAppDetailPrimaryUid();
|
||||||
try {
|
try {
|
||||||
mPolicyService.setUidPolicy(
|
mPolicyService.setUidPolicy(primaryUid,
|
||||||
mUid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
|
restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
throw new RuntimeException("unable to save policy", e);
|
throw new RuntimeException("unable to save policy", e);
|
||||||
}
|
}
|
||||||
@@ -851,8 +888,8 @@ public class DataUsageSummary extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate, true);
|
final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate);
|
||||||
if (isNetworkPolicyModifiable()) {
|
if (isNetworkPolicyModifiable(policy)) {
|
||||||
mDisableAtLimitView.setVisibility(View.VISIBLE);
|
mDisableAtLimitView.setVisibility(View.VISIBLE);
|
||||||
mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
|
mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
|
||||||
if (!isAppDetailMode()) {
|
if (!isAppDetailMode()) {
|
||||||
@@ -912,19 +949,28 @@ public class DataUsageSummary extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// one last cycle entry to modify policy cycle day
|
// one last cycle entry to modify policy cycle day
|
||||||
mCycleAdapter.setChangePossible(isNetworkPolicyModifiable());
|
mCycleAdapter.setChangePossible(isNetworkPolicyModifiable(policy));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasCycles) {
|
if (!hasCycles) {
|
||||||
// no valid cycles; show all data
|
// no policy defined cycles; show entry for each four-week period
|
||||||
// TODO: offer simple ranges like "last week" etc
|
long cycleEnd = historyEnd;
|
||||||
mCycleAdapter.add(new CycleItem(context, historyStart, historyEnd));
|
while (cycleEnd > historyStart) {
|
||||||
|
final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4);
|
||||||
|
mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
|
||||||
|
cycleEnd = cycleStart;
|
||||||
|
}
|
||||||
|
|
||||||
mCycleAdapter.setChangePossible(false);
|
mCycleAdapter.setChangePossible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// force pick the current cycle (first item)
|
// force pick the current cycle (first item)
|
||||||
|
if (mCycleAdapter.getCount() > 0) {
|
||||||
mCycleSpinner.setSelection(0);
|
mCycleSpinner.setSelection(0);
|
||||||
mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
|
mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
|
||||||
|
} else {
|
||||||
|
updateDetailData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private OnCheckedChangeListener mDataEnabledListener = new OnCheckedChangeListener() {
|
private OnCheckedChangeListener mDataEnabledListener = new OnCheckedChangeListener() {
|
||||||
@@ -985,8 +1031,10 @@ public class DataUsageSummary extends Fragment {
|
|||||||
private OnItemClickListener mListListener = new OnItemClickListener() {
|
private OnItemClickListener mListListener = new OnItemClickListener() {
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
final Context context = view.getContext();
|
||||||
final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
|
final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
|
||||||
AppDetailsFragment.show(DataUsageSummary.this, app.uid);
|
final UidDetail detail = resolveDetailForUid(context, app.uids[0]);
|
||||||
|
AppDetailsFragment.show(DataUsageSummary.this, app.uids, detail.label);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1065,6 +1113,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
entry = mHistory.getValues(start, end, now, null);
|
entry = mHistory.getValues(start, end, now, null);
|
||||||
|
|
||||||
// kick off loader for detailed stats
|
// kick off loader for detailed stats
|
||||||
|
// TODO: delay loader until animation is finished
|
||||||
getLoaderManager().restartLoader(LOADER_SUMMARY,
|
getLoaderManager().restartLoader(LOADER_SUMMARY,
|
||||||
SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryForAllUid);
|
SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryForAllUid);
|
||||||
}
|
}
|
||||||
@@ -1222,9 +1271,20 @@ public class DataUsageSummary extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class AppUsageItem implements Comparable<AppUsageItem> {
|
private static class AppUsageItem implements Comparable<AppUsageItem> {
|
||||||
public int uid;
|
public int[] uids;
|
||||||
public long total;
|
public long total;
|
||||||
|
|
||||||
|
public AppUsageItem(int uid) {
|
||||||
|
uids = new int[] { uid };
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addUid(int uid) {
|
||||||
|
if (contains(uids, uid)) return;
|
||||||
|
final int length = uids.length;
|
||||||
|
uids = Arrays.copyOf(uids, length + 1);
|
||||||
|
uids[length] = uid;
|
||||||
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
public int compareTo(AppUsageItem another) {
|
public int compareTo(AppUsageItem another) {
|
||||||
return Long.compare(another.total, total);
|
return Long.compare(another.total, total);
|
||||||
@@ -1244,9 +1304,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
public void bindStats(NetworkStats stats) {
|
public void bindStats(NetworkStats stats) {
|
||||||
mItems.clear();
|
mItems.clear();
|
||||||
|
|
||||||
final AppUsageItem systemItem = new AppUsageItem();
|
final AppUsageItem systemItem = new AppUsageItem(android.os.Process.SYSTEM_UID);
|
||||||
systemItem.uid = android.os.Process.SYSTEM_UID;
|
|
||||||
|
|
||||||
final SparseArray<AppUsageItem> knownUids = new SparseArray<AppUsageItem>();
|
final SparseArray<AppUsageItem> knownUids = new SparseArray<AppUsageItem>();
|
||||||
|
|
||||||
NetworkStats.Entry entry = null;
|
NetworkStats.Entry entry = null;
|
||||||
@@ -1260,8 +1318,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
if (isApp || uid == TrafficStats.UID_REMOVED) {
|
if (isApp || uid == TrafficStats.UID_REMOVED) {
|
||||||
AppUsageItem item = knownUids.get(uid);
|
AppUsageItem item = knownUids.get(uid);
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
item = new AppUsageItem();
|
item = new AppUsageItem(uid);
|
||||||
item.uid = uid;
|
|
||||||
knownUids.put(uid, item);
|
knownUids.put(uid, item);
|
||||||
mItems.add(item);
|
mItems.add(item);
|
||||||
}
|
}
|
||||||
@@ -1269,6 +1326,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
item.total += entry.rxBytes + entry.txBytes;
|
item.total += entry.rxBytes + entry.txBytes;
|
||||||
} else {
|
} else {
|
||||||
systemItem.total += entry.rxBytes + entry.txBytes;
|
systemItem.total += entry.rxBytes + entry.txBytes;
|
||||||
|
systemItem.addUid(uid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1293,7 +1351,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getItemId(int position) {
|
public long getItemId(int position) {
|
||||||
return mItems.get(position).uid;
|
return mItems.get(position).uids[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1312,7 +1370,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
android.R.id.progress);
|
android.R.id.progress);
|
||||||
|
|
||||||
final AppUsageItem item = mItems.get(position);
|
final AppUsageItem item = mItems.get(position);
|
||||||
final UidDetail detail = resolveDetailForUid(context, item.uid);
|
final UidDetail detail = resolveDetailForUid(context, item.uids[0]);
|
||||||
|
|
||||||
icon.setImageDrawable(detail.icon);
|
icon.setImageDrawable(detail.icon);
|
||||||
title.setText(detail.label);
|
title.setText(detail.label);
|
||||||
@@ -1323,7 +1381,6 @@ public class DataUsageSummary extends Fragment {
|
|||||||
|
|
||||||
return convertView;
|
return convertView;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1331,11 +1388,11 @@ public class DataUsageSummary extends Fragment {
|
|||||||
* {@link DataUsageSummary}.
|
* {@link DataUsageSummary}.
|
||||||
*/
|
*/
|
||||||
public static class AppDetailsFragment extends Fragment {
|
public static class AppDetailsFragment extends Fragment {
|
||||||
private static final String EXTRA_UID = "uid";
|
private static final String EXTRA_UIDS = "uids";
|
||||||
|
|
||||||
public static void show(DataUsageSummary parent, int uid) {
|
public static void show(DataUsageSummary parent, int[] uids, CharSequence label) {
|
||||||
final Bundle args = new Bundle();
|
final Bundle args = new Bundle();
|
||||||
args.putInt(EXTRA_UID, uid);
|
args.putIntArray(EXTRA_UIDS, uids);
|
||||||
|
|
||||||
final AppDetailsFragment fragment = new AppDetailsFragment();
|
final AppDetailsFragment fragment = new AppDetailsFragment();
|
||||||
fragment.setArguments(args);
|
fragment.setArguments(args);
|
||||||
@@ -1344,6 +1401,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
final FragmentTransaction ft = parent.getFragmentManager().beginTransaction();
|
final FragmentTransaction ft = parent.getFragmentManager().beginTransaction();
|
||||||
ft.add(fragment, TAG_APP_DETAILS);
|
ft.add(fragment, TAG_APP_DETAILS);
|
||||||
ft.addToBackStack(TAG_APP_DETAILS);
|
ft.addToBackStack(TAG_APP_DETAILS);
|
||||||
|
ft.setBreadCrumbTitle(label);
|
||||||
ft.commit();
|
ft.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1351,7 +1409,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
|
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
|
||||||
target.mUid = getArguments().getInt(EXTRA_UID);
|
target.mAppDetailUids = getArguments().getIntArray(EXTRA_UIDS);
|
||||||
target.updateBody();
|
target.updateBody();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1359,7 +1417,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
|
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
|
||||||
target.mUid = UID_NONE;
|
target.mAppDetailUids = null;
|
||||||
target.updateBody();
|
target.updateBody();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1441,7 +1499,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
private 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, false);
|
final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate);
|
||||||
final Bundle args = new Bundle();
|
final Bundle args = new Bundle();
|
||||||
args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay);
|
args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay);
|
||||||
|
|
||||||
@@ -1807,4 +1865,13 @@ public class DataUsageSummary extends Fragment {
|
|||||||
summary.setVisibility(View.VISIBLE);
|
summary.setVisibility(View.VISIBLE);
|
||||||
summary.setText(string);
|
summary.setText(string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean contains(int[] haystack, int needle) {
|
||||||
|
for (int value : haystack) {
|
||||||
|
if (value == needle) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import static android.net.NetworkPolicy.SNOOZE_NEVER;
|
|||||||
import static android.net.NetworkPolicy.WARNING_DISABLED;
|
import static android.net.NetworkPolicy.WARNING_DISABLED;
|
||||||
import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
|
import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
|
||||||
import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
|
import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
|
||||||
|
import static android.net.NetworkTemplate.MATCH_WIFI;
|
||||||
import static android.net.NetworkTemplate.buildTemplateMobile3gLower;
|
import static android.net.NetworkTemplate.buildTemplateMobile3gLower;
|
||||||
import static android.net.NetworkTemplate.buildTemplateMobile4g;
|
import static android.net.NetworkTemplate.buildTemplateMobile4g;
|
||||||
import static android.net.NetworkTemplate.buildTemplateMobileAll;
|
import static android.net.NetworkTemplate.buildTemplateMobileAll;
|
||||||
@@ -40,7 +41,7 @@ import java.util.ArrayList;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class to modify list of {@link NetworkPolicy}. Specifically knows
|
* Utility class to modify list of {@link NetworkPolicy}. Specifically knows
|
||||||
* about which policies can coexist.
|
* about which policies can coexist. Not thread safe.
|
||||||
*/
|
*/
|
||||||
public class NetworkPolicyEditor {
|
public class NetworkPolicyEditor {
|
||||||
// TODO: be more robust when missing policies from service
|
// TODO: be more robust when missing policies from service
|
||||||
@@ -53,65 +54,81 @@ public class NetworkPolicyEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void read() {
|
public void read() {
|
||||||
|
final NetworkPolicy[] policies;
|
||||||
try {
|
try {
|
||||||
final NetworkPolicy[] policies = mPolicyService.getNetworkPolicies();
|
policies = mPolicyService.getNetworkPolicies();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
throw new RuntimeException("problem reading policies", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean modified = false;
|
||||||
mPolicies.clear();
|
mPolicies.clear();
|
||||||
for (NetworkPolicy policy : policies) {
|
for (NetworkPolicy policy : policies) {
|
||||||
// TODO: find better place to clamp these
|
// TODO: find better place to clamp these
|
||||||
if (policy.limitBytes < -1) {
|
if (policy.limitBytes < -1) {
|
||||||
policy.limitBytes = LIMIT_DISABLED;
|
policy.limitBytes = LIMIT_DISABLED;
|
||||||
|
modified = true;
|
||||||
}
|
}
|
||||||
if (policy.warningBytes < -1) {
|
if (policy.warningBytes < -1) {
|
||||||
policy.warningBytes = WARNING_DISABLED;
|
policy.warningBytes = WARNING_DISABLED;
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// drop any WIFI policies that were defined
|
||||||
|
if (policy.template.getMatchRule() == MATCH_WIFI) {
|
||||||
|
modified = true;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
mPolicies.add(policy);
|
mPolicies.add(policy);
|
||||||
}
|
}
|
||||||
} catch (RemoteException e) {
|
|
||||||
throw new RuntimeException("problem reading policies", e);
|
// when we cleaned policies above, write back changes
|
||||||
}
|
if (modified) writeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeAsync() {
|
public void writeAsync() {
|
||||||
// TODO: consider making more robust by passing through service
|
// TODO: consider making more robust by passing through service
|
||||||
|
final NetworkPolicy[] policies = mPolicies.toArray(new NetworkPolicy[mPolicies.size()]);
|
||||||
new AsyncTask<Void, Void, Void>() {
|
new AsyncTask<Void, Void, Void>() {
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... params) {
|
protected Void doInBackground(Void... params) {
|
||||||
write();
|
write(policies);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}.execute();
|
}.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write() {
|
public void write(NetworkPolicy[] policies) {
|
||||||
try {
|
try {
|
||||||
final NetworkPolicy[] policies = mPolicies.toArray(new NetworkPolicy[mPolicies.size()]);
|
|
||||||
mPolicyService.setNetworkPolicies(policies);
|
mPolicyService.setNetworkPolicies(policies);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
throw new RuntimeException("problem reading policies", e);
|
throw new RuntimeException("problem writing policies", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasLimitedPolicy(NetworkTemplate template) {
|
public boolean hasLimitedPolicy(NetworkTemplate template) {
|
||||||
final NetworkPolicy policy = getPolicy(template, false);
|
final NetworkPolicy policy = getPolicy(template);
|
||||||
return policy != null && policy.limitBytes != LIMIT_DISABLED;
|
return policy != null && policy.limitBytes != LIMIT_DISABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NetworkPolicy getPolicy(NetworkTemplate template, boolean createDefault) {
|
public NetworkPolicy getOrCreatePolicy(NetworkTemplate template) {
|
||||||
|
NetworkPolicy policy = getPolicy(template);
|
||||||
|
if (policy == null) {
|
||||||
|
policy = buildDefaultPolicy(template);
|
||||||
|
mPolicies.add(policy);
|
||||||
|
}
|
||||||
|
return policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkPolicy getPolicy(NetworkTemplate template) {
|
||||||
for (NetworkPolicy policy : mPolicies) {
|
for (NetworkPolicy policy : mPolicies) {
|
||||||
if (policy.template.equals(template)) {
|
if (policy.template.equals(template)) {
|
||||||
return policy;
|
return policy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (createDefault) {
|
|
||||||
final NetworkPolicy policy = buildDefaultPolicy(template);
|
|
||||||
mPolicies.add(policy);
|
|
||||||
return policy;
|
|
||||||
} else {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static NetworkPolicy buildDefaultPolicy(NetworkTemplate template) {
|
private static NetworkPolicy buildDefaultPolicy(NetworkTemplate template) {
|
||||||
// TODO: move this into framework to share with NetworkPolicyManagerService
|
// TODO: move this into framework to share with NetworkPolicyManagerService
|
||||||
@@ -124,21 +141,21 @@ public class NetworkPolicyEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setPolicyCycleDay(NetworkTemplate template, int cycleDay) {
|
public void setPolicyCycleDay(NetworkTemplate template, int cycleDay) {
|
||||||
final NetworkPolicy policy = getPolicy(template, true);
|
final NetworkPolicy policy = getOrCreatePolicy(template);
|
||||||
policy.cycleDay = cycleDay;
|
policy.cycleDay = cycleDay;
|
||||||
policy.lastSnooze = SNOOZE_NEVER;
|
policy.lastSnooze = SNOOZE_NEVER;
|
||||||
writeAsync();
|
writeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPolicyWarningBytes(NetworkTemplate template, long warningBytes) {
|
public void setPolicyWarningBytes(NetworkTemplate template, long warningBytes) {
|
||||||
final NetworkPolicy policy = getPolicy(template, true);
|
final NetworkPolicy policy = getOrCreatePolicy(template);
|
||||||
policy.warningBytes = warningBytes;
|
policy.warningBytes = warningBytes;
|
||||||
policy.lastSnooze = SNOOZE_NEVER;
|
policy.lastSnooze = SNOOZE_NEVER;
|
||||||
writeAsync();
|
writeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPolicyLimitBytes(NetworkTemplate template, long limitBytes) {
|
public void setPolicyLimitBytes(NetworkTemplate template, long limitBytes) {
|
||||||
final NetworkPolicy policy = getPolicy(template, true);
|
final NetworkPolicy policy = getOrCreatePolicy(template);
|
||||||
policy.limitBytes = limitBytes;
|
policy.limitBytes = limitBytes;
|
||||||
policy.lastSnooze = SNOOZE_NEVER;
|
policy.lastSnooze = SNOOZE_NEVER;
|
||||||
writeAsync();
|
writeAsync();
|
||||||
@@ -176,8 +193,8 @@ public class NetworkPolicyEditor {
|
|||||||
|
|
||||||
} else if (beforeSplit && !split) {
|
} else if (beforeSplit && !split) {
|
||||||
// combine, picking most restrictive policy
|
// combine, picking most restrictive policy
|
||||||
final NetworkPolicy policy3g = getPolicy(template3g, false);
|
final NetworkPolicy policy3g = getPolicy(template3g);
|
||||||
final NetworkPolicy policy4g = getPolicy(template4g, false);
|
final NetworkPolicy policy4g = getPolicy(template4g);
|
||||||
|
|
||||||
final NetworkPolicy restrictive = policy3g.compareTo(policy4g) < 0 ? policy3g
|
final NetworkPolicy restrictive = policy3g.compareTo(policy4g) < 0 ? policy3g
|
||||||
: policy4g;
|
: policy4g;
|
||||||
@@ -190,7 +207,7 @@ public class NetworkPolicyEditor {
|
|||||||
|
|
||||||
} else if (!beforeSplit && split) {
|
} else if (!beforeSplit && split) {
|
||||||
// duplicate existing policy into two rules
|
// duplicate existing policy into two rules
|
||||||
final NetworkPolicy policyAll = getPolicy(templateAll, false);
|
final NetworkPolicy policyAll = getPolicy(templateAll);
|
||||||
mPolicies.remove(policyAll);
|
mPolicies.remove(policyAll);
|
||||||
mPolicies.add(
|
mPolicies.add(
|
||||||
new NetworkPolicy(template3g, policyAll.cycleDay, policyAll.warningBytes,
|
new NetworkPolicy(template3g, policyAll.cycleDay, policyAll.warningBytes,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import android.text.SpannableStringBuilder;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.format.DateUtils;
|
import android.text.format.DateUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.view.Gravity;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ import com.android.settings.widget.ChartSweepView.OnSweepListener;
|
|||||||
* Specific {@link ChartView} that displays {@link ChartNetworkSeriesView} along
|
* Specific {@link ChartView} that displays {@link ChartNetworkSeriesView} along
|
||||||
* with {@link ChartSweepView} for inspection ranges and warning/limits.
|
* with {@link ChartSweepView} for inspection ranges and warning/limits.
|
||||||
*/
|
*/
|
||||||
public class DataUsageChartView extends ChartView {
|
public class ChartDataUsageView extends ChartView {
|
||||||
|
|
||||||
private static final long KB_IN_BYTES = 1024;
|
private static final long KB_IN_BYTES = 1024;
|
||||||
private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
|
private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
|
||||||
@@ -46,6 +47,8 @@ public class DataUsageChartView extends ChartView {
|
|||||||
private static final int MSG_UPDATE_AXIS = 100;
|
private static final int MSG_UPDATE_AXIS = 100;
|
||||||
private static final long DELAY_MILLIS = 250;
|
private static final long DELAY_MILLIS = 250;
|
||||||
|
|
||||||
|
private static final boolean LIMIT_SWEEPS_TO_VALID_DATA = false;
|
||||||
|
|
||||||
private ChartGridView mGrid;
|
private ChartGridView mGrid;
|
||||||
private ChartNetworkSeriesView mSeries;
|
private ChartNetworkSeriesView mSeries;
|
||||||
private ChartNetworkSeriesView mDetailSeries;
|
private ChartNetworkSeriesView mDetailSeries;
|
||||||
@@ -70,15 +73,15 @@ public class DataUsageChartView extends ChartView {
|
|||||||
|
|
||||||
private DataUsageChartListener mListener;
|
private DataUsageChartListener mListener;
|
||||||
|
|
||||||
public DataUsageChartView(Context context) {
|
public ChartDataUsageView(Context context) {
|
||||||
this(context, null, 0);
|
this(context, null, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DataUsageChartView(Context context, AttributeSet attrs) {
|
public ChartDataUsageView(Context context, AttributeSet attrs) {
|
||||||
this(context, attrs, 0);
|
this(context, attrs, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DataUsageChartView(Context context, AttributeSet attrs, int defStyle) {
|
public ChartDataUsageView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
init(new TimeAxis(), new InvertedChartAxis(new DataAxis()));
|
init(new TimeAxis(), new InvertedChartAxis(new DataAxis()));
|
||||||
|
|
||||||
@@ -186,6 +189,7 @@ public class DataUsageChartView extends ChartView {
|
|||||||
|
|
||||||
updateVertAxisBounds(null);
|
updateVertAxisBounds(null);
|
||||||
requestLayout();
|
requestLayout();
|
||||||
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -194,7 +198,8 @@ public class DataUsageChartView extends ChartView {
|
|||||||
*/
|
*/
|
||||||
private void updateVertAxisBounds(ChartSweepView activeSweep) {
|
private void updateVertAxisBounds(ChartSweepView activeSweep) {
|
||||||
final long max = mVertMax;
|
final long max = mVertMax;
|
||||||
final long newMax;
|
|
||||||
|
long newMax = 0;
|
||||||
if (activeSweep != null) {
|
if (activeSweep != null) {
|
||||||
final int adjustAxis = activeSweep.shouldAdjustAxis();
|
final int adjustAxis = activeSweep.shouldAdjustAxis();
|
||||||
if (adjustAxis > 0) {
|
if (adjustAxis > 0) {
|
||||||
@@ -206,13 +211,13 @@ public class DataUsageChartView extends ChartView {
|
|||||||
} else {
|
} else {
|
||||||
newMax = max;
|
newMax = max;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
// always show known data and policy lines
|
||||||
// try showing all known data and policy
|
|
||||||
final long maxSweep = Math.max(mSweepWarning.getValue(), mSweepLimit.getValue());
|
final long maxSweep = Math.max(mSweepWarning.getValue(), mSweepLimit.getValue());
|
||||||
final long maxVisible = Math.max(mSeries.getMaxVisible(), maxSweep) * 12 / 10;
|
final long maxVisible = Math.max(mSeries.getMaxVisible(), maxSweep) * 12 / 10;
|
||||||
newMax = Math.max(maxVisible, 2 * GB_IN_BYTES);
|
final long maxDefault = Math.max(maxVisible, 2 * GB_IN_BYTES);
|
||||||
}
|
newMax = Math.max(maxDefault, newMax);
|
||||||
|
|
||||||
// only invalidate when vertMax actually changed
|
// only invalidate when vertMax actually changed
|
||||||
if (newMax != mVertMax) {
|
if (newMax != mVertMax) {
|
||||||
@@ -231,6 +236,16 @@ public class DataUsageChartView extends ChartView {
|
|||||||
if (activeSweep != null) {
|
if (activeSweep != null) {
|
||||||
activeSweep.updateValueFromPosition();
|
activeSweep.updateValueFromPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// layout other sweeps to match changed axis
|
||||||
|
// TODO: find cleaner way of doing this, such as requesting full
|
||||||
|
// layout and making activeSweep discard its tracking MotionEvent.
|
||||||
|
if (mSweepLimit != activeSweep) {
|
||||||
|
layoutSweep(mSweepLimit);
|
||||||
|
}
|
||||||
|
if (mSweepWarning != activeSweep) {
|
||||||
|
layoutSweep(mSweepWarning);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,9 +361,14 @@ public class DataUsageChartView extends ChartView {
|
|||||||
final long validStart = Math.max(visibleStart, getStatsStart());
|
final long validStart = Math.max(visibleStart, getStatsStart());
|
||||||
final long validEnd = Math.min(visibleEnd, getStatsEnd());
|
final long validEnd = Math.min(visibleEnd, getStatsEnd());
|
||||||
|
|
||||||
|
if (LIMIT_SWEEPS_TO_VALID_DATA) {
|
||||||
// prevent time sweeps from leaving valid data
|
// prevent time sweeps from leaving valid data
|
||||||
mSweepLeft.setValidRange(validStart, validEnd);
|
mSweepLeft.setValidRange(validStart, validEnd);
|
||||||
mSweepRight.setValidRange(validStart, validEnd);
|
mSweepRight.setValidRange(validStart, validEnd);
|
||||||
|
} else {
|
||||||
|
mSweepLeft.setValidRange(visibleStart, visibleEnd);
|
||||||
|
mSweepRight.setValidRange(visibleStart, visibleEnd);
|
||||||
|
}
|
||||||
|
|
||||||
// default sweeps to last week of data
|
// default sweeps to last week of data
|
||||||
final long halfRange = (visibleEnd + visibleStart) / 2;
|
final long halfRange = (visibleEnd + visibleStart) / 2;
|
||||||
@@ -424,7 +444,7 @@ public class DataUsageChartView extends ChartView {
|
|||||||
final int tickCount = (int) ((mMax - mMin) / TICK_INTERVAL);
|
final int tickCount = (int) ((mMax - mMin) / TICK_INTERVAL);
|
||||||
final float[] tickPoints = new float[tickCount];
|
final float[] tickPoints = new float[tickCount];
|
||||||
for (int i = 0; i < tickCount; i++) {
|
for (int i = 0; i < tickCount; i++) {
|
||||||
tickPoints[i] = convertToPoint(mMax - (TICK_INTERVAL * i));
|
tickPoints[i] = convertToPoint(mMax - (TICK_INTERVAL * (i + 1)));
|
||||||
}
|
}
|
||||||
return tickPoints;
|
return tickPoints;
|
||||||
}
|
}
|
||||||
@@ -501,7 +521,14 @@ public class DataUsageChartView extends ChartView {
|
|||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
public float[] getTickPoints() {
|
public float[] getTickPoints() {
|
||||||
final long range = mMax - mMin;
|
final long range = mMax - mMin;
|
||||||
final long tickJump = 256 * MB_IN_BYTES;
|
final long tickJump;
|
||||||
|
if (range < 6 * GB_IN_BYTES) {
|
||||||
|
tickJump = 256 * MB_IN_BYTES;
|
||||||
|
} else if (range < 12 * GB_IN_BYTES) {
|
||||||
|
tickJump = 512 * MB_IN_BYTES;
|
||||||
|
} else {
|
||||||
|
tickJump = 1 * GB_IN_BYTES;
|
||||||
|
}
|
||||||
|
|
||||||
final int tickCount = (int) (range / tickJump);
|
final int tickCount = (int) (range / tickJump);
|
||||||
final float[] tickPoints = new float[tickCount];
|
final float[] tickPoints = new float[tickCount];
|
||||||
@@ -21,6 +21,7 @@ import android.content.res.TypedArray;
|
|||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Paint.Style;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
@@ -42,8 +43,14 @@ import com.google.common.base.Preconditions;
|
|||||||
*/
|
*/
|
||||||
public class ChartSweepView extends View {
|
public class ChartSweepView extends View {
|
||||||
|
|
||||||
|
private static final boolean DRAW_OUTLINE = false;
|
||||||
|
|
||||||
private Drawable mSweep;
|
private Drawable mSweep;
|
||||||
private Rect mSweepPadding = new Rect();
|
private Rect mSweepPadding = new Rect();
|
||||||
|
|
||||||
|
/** Offset of content inside this view. */
|
||||||
|
private Point mContentOffset = new Point();
|
||||||
|
/** Offset of {@link #mSweep} inside this view. */
|
||||||
private Point mSweepOffset = new Point();
|
private Point mSweepOffset = new Point();
|
||||||
|
|
||||||
private Rect mMargins = new Rect();
|
private Rect mMargins = new Rect();
|
||||||
@@ -66,6 +73,8 @@ public class ChartSweepView extends View {
|
|||||||
private ChartSweepView mValidAfterDynamic;
|
private ChartSweepView mValidAfterDynamic;
|
||||||
private ChartSweepView mValidBeforeDynamic;
|
private ChartSweepView mValidBeforeDynamic;
|
||||||
|
|
||||||
|
private Paint mOutlinePaint = new Paint();
|
||||||
|
|
||||||
public static final int HORIZONTAL = 0;
|
public static final int HORIZONTAL = 0;
|
||||||
public static final int VERTICAL = 1;
|
public static final int VERTICAL = 1;
|
||||||
|
|
||||||
@@ -98,6 +107,10 @@ public class ChartSweepView extends View {
|
|||||||
setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0));
|
setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0));
|
||||||
setLabelColor(a.getColor(R.styleable.ChartSweepView_labelColor, Color.BLUE));
|
setLabelColor(a.getColor(R.styleable.ChartSweepView_labelColor, Color.BLUE));
|
||||||
|
|
||||||
|
mOutlinePaint.setColor(Color.RED);
|
||||||
|
mOutlinePaint.setStrokeWidth(1f);
|
||||||
|
mOutlinePaint.setStyle(Style.STROKE);
|
||||||
|
|
||||||
a.recycle();
|
a.recycle();
|
||||||
|
|
||||||
setWillNotDraw(false);
|
setWillNotDraw(false);
|
||||||
@@ -123,11 +136,11 @@ public class ChartSweepView extends View {
|
|||||||
if (mFollowAxis == VERTICAL) {
|
if (mFollowAxis == VERTICAL) {
|
||||||
final float targetHeight = mSweep.getIntrinsicHeight() - mSweepPadding.top
|
final float targetHeight = mSweep.getIntrinsicHeight() - mSweepPadding.top
|
||||||
- mSweepPadding.bottom;
|
- mSweepPadding.bottom;
|
||||||
return mSweepPadding.top + (targetHeight / 2);
|
return mSweepPadding.top + (targetHeight / 2) + mSweepOffset.y;
|
||||||
} else {
|
} else {
|
||||||
final float targetWidth = mSweep.getIntrinsicWidth() - mSweepPadding.left
|
final float targetWidth = mSweep.getIntrinsicWidth() - mSweepPadding.left
|
||||||
- mSweepPadding.right;
|
- mSweepPadding.right;
|
||||||
return mSweepPadding.left + (targetWidth / 2);
|
return mSweepPadding.left + (targetWidth / 2) + mSweepOffset.x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,6 +208,7 @@ public class ChartSweepView extends View {
|
|||||||
paint.density = getResources().getDisplayMetrics().density;
|
paint.density = getResources().getDisplayMetrics().density;
|
||||||
paint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
|
paint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
|
||||||
paint.setColor(mLabelColor);
|
paint.setColor(mLabelColor);
|
||||||
|
paint.setShadowLayer(4 * paint.density, 0, 0, Color.BLACK);
|
||||||
|
|
||||||
mLabelTemplate = new SpannableStringBuilder(template);
|
mLabelTemplate = new SpannableStringBuilder(template);
|
||||||
mLabelLayout = new DynamicLayout(
|
mLabelLayout = new DynamicLayout(
|
||||||
@@ -283,6 +297,26 @@ public class ChartSweepView extends View {
|
|||||||
mValidBeforeDynamic = validBefore;
|
mValidBeforeDynamic = validBefore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if given {@link MotionEvent} is closer to another
|
||||||
|
* {@link ChartSweepView} compared to ourselves.
|
||||||
|
*/
|
||||||
|
public boolean isTouchCloserTo(MotionEvent eventInParent, ChartSweepView another) {
|
||||||
|
if (another == null) return false;
|
||||||
|
|
||||||
|
if (mFollowAxis == HORIZONTAL) {
|
||||||
|
final float selfDist = Math.abs(eventInParent.getX() - (getX() + getTargetInset()));
|
||||||
|
final float anotherDist = Math.abs(
|
||||||
|
eventInParent.getX() - (another.getX() + another.getTargetInset()));
|
||||||
|
return anotherDist < selfDist;
|
||||||
|
} else {
|
||||||
|
final float selfDist = Math.abs(eventInParent.getY() - (getY() + getTargetInset()));
|
||||||
|
final float anotherDist = Math.abs(
|
||||||
|
eventInParent.getY() - (another.getY() + another.getTargetInset()));
|
||||||
|
return anotherDist < selfDist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onTouchEvent(MotionEvent event) {
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
if (!isEnabled()) return false;
|
if (!isEnabled()) return false;
|
||||||
@@ -294,9 +328,18 @@ public class ChartSweepView extends View {
|
|||||||
// only start tracking when in sweet spot
|
// only start tracking when in sweet spot
|
||||||
final boolean accept;
|
final boolean accept;
|
||||||
if (mFollowAxis == VERTICAL) {
|
if (mFollowAxis == VERTICAL) {
|
||||||
accept = event.getX() > getWidth() - (mSweepPadding.right * 2);
|
accept = event.getX() > getWidth() - (mSweepPadding.right * 3);
|
||||||
} else {
|
} else {
|
||||||
accept = event.getY() > getHeight() - (mSweepPadding.bottom * 2);
|
accept = event.getY() > getHeight() - (mSweepPadding.bottom * 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
final MotionEvent eventInParent = event.copy();
|
||||||
|
eventInParent.offsetLocation(getLeft(), getTop());
|
||||||
|
|
||||||
|
// ignore event when closer to a neighbor
|
||||||
|
if (isTouchCloserTo(eventInParent, mValidAfterDynamic)
|
||||||
|
|| isTouchCloserTo(eventInParent, mValidBeforeDynamic)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accept) {
|
if (accept) {
|
||||||
@@ -460,6 +503,7 @@ public class ChartSweepView extends View {
|
|||||||
final int templateHeight = mLabelLayout.getHeight();
|
final int templateHeight = mLabelLayout.getHeight();
|
||||||
|
|
||||||
mSweepOffset.x = 0;
|
mSweepOffset.x = 0;
|
||||||
|
mSweepOffset.y = 0;
|
||||||
mSweepOffset.y = (int) ((templateHeight / 2) - getTargetInset());
|
mSweepOffset.y = (int) ((templateHeight / 2) - getTargetInset());
|
||||||
setMeasuredDimension(mSweep.getIntrinsicWidth(), Math.max(sweepHeight, templateHeight));
|
setMeasuredDimension(mSweep.getIntrinsicWidth(), Math.max(sweepHeight, templateHeight));
|
||||||
|
|
||||||
@@ -485,6 +529,23 @@ public class ChartSweepView extends View {
|
|||||||
mMargins.bottom = mSweepPadding.bottom;
|
mMargins.bottom = mSweepPadding.bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mContentOffset.x = 0;
|
||||||
|
mContentOffset.y = 0;
|
||||||
|
|
||||||
|
// make touch target area larger
|
||||||
|
if (mFollowAxis == HORIZONTAL) {
|
||||||
|
final int widthBefore = getMeasuredWidth();
|
||||||
|
final int widthAfter = widthBefore * 3;
|
||||||
|
setMeasuredDimension(widthAfter, getMeasuredHeight());
|
||||||
|
mContentOffset.offset((widthAfter - widthBefore) / 2, 0);
|
||||||
|
} else {
|
||||||
|
final int heightBefore = getMeasuredHeight();
|
||||||
|
final int heightAfter = heightBefore * 3;
|
||||||
|
setMeasuredDimension(getMeasuredWidth(), heightAfter);
|
||||||
|
mContentOffset.offset(0, (heightAfter - heightBefore) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
mSweepOffset.offset(mContentOffset.x, mContentOffset.y);
|
||||||
mMargins.offset(-mSweepOffset.x, -mSweepOffset.y);
|
mMargins.offset(-mSweepOffset.x, -mSweepOffset.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,9 +554,43 @@ public class ChartSweepView extends View {
|
|||||||
final int width = getWidth();
|
final int width = getWidth();
|
||||||
final int height = getHeight();
|
final int height = getHeight();
|
||||||
|
|
||||||
|
if (DRAW_OUTLINE) {
|
||||||
|
canvas.drawRect(0, 0, width, height, mOutlinePaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// when overlapping with neighbor, split difference and push label
|
||||||
|
float margin;
|
||||||
|
float labelOffset = 0;
|
||||||
|
if (mFollowAxis == VERTICAL) {
|
||||||
|
if (mValidAfterDynamic != null) {
|
||||||
|
margin = getLabelTop(mValidAfterDynamic) - getLabelBottom(this);
|
||||||
|
if (margin < 0) {
|
||||||
|
labelOffset = margin / 2;
|
||||||
|
}
|
||||||
|
} else if (mValidBeforeDynamic != null) {
|
||||||
|
margin = getLabelTop(this) - getLabelBottom(mValidBeforeDynamic);
|
||||||
|
if (margin < 0) {
|
||||||
|
labelOffset = -margin / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: implement horizontal labels
|
||||||
|
}
|
||||||
|
|
||||||
|
// when offsetting label, neighbor probably needs to offset too
|
||||||
|
if (labelOffset != 0) {
|
||||||
|
if (mValidAfterDynamic != null) mValidAfterDynamic.invalidate();
|
||||||
|
if (mValidBeforeDynamic != null) mValidBeforeDynamic.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
final int labelSize;
|
final int labelSize;
|
||||||
if (isEnabled() && mLabelLayout != null) {
|
if (isEnabled() && mLabelLayout != null) {
|
||||||
|
final int count = canvas.save();
|
||||||
|
{
|
||||||
|
canvas.translate(mContentOffset.x, mContentOffset.y + labelOffset);
|
||||||
mLabelLayout.draw(canvas);
|
mLabelLayout.draw(canvas);
|
||||||
|
}
|
||||||
|
canvas.restoreToCount(count);
|
||||||
labelSize = mLabelSize;
|
labelSize = mLabelSize;
|
||||||
} else {
|
} else {
|
||||||
labelSize = 0;
|
labelSize = 0;
|
||||||
@@ -512,4 +607,11 @@ public class ChartSweepView extends View {
|
|||||||
mSweep.draw(canvas);
|
mSweep.draw(canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static float getLabelTop(ChartSweepView view) {
|
||||||
|
return view.getY() + view.mContentOffset.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float getLabelBottom(ChartSweepView view) {
|
||||||
|
return getLabelTop(view) + view.mLabelLayout.getHeight();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,6 @@ import com.android.settings.R;
|
|||||||
* and screen coordinates.
|
* and screen coordinates.
|
||||||
*/
|
*/
|
||||||
public class ChartView extends FrameLayout {
|
public class ChartView extends FrameLayout {
|
||||||
private static final String TAG = "ChartView";
|
|
||||||
|
|
||||||
// TODO: extend something that supports two-dimensional scrolling
|
// TODO: extend something that supports two-dimensional scrolling
|
||||||
|
|
||||||
private static final int SWEEP_GRAVITY = Gravity.TOP | Gravity.LEFT;
|
private static final int SWEEP_GRAVITY = Gravity.TOP | Gravity.LEFT;
|
||||||
@@ -122,16 +120,30 @@ public class ChartView extends FrameLayout {
|
|||||||
child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
|
child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
|
||||||
|
|
||||||
} else if (child instanceof ChartSweepView) {
|
} else if (child instanceof ChartSweepView) {
|
||||||
// sweep is always placed along specific dimension
|
layoutSweep((ChartSweepView) child, parentRect, childRect);
|
||||||
final ChartSweepView sweep = (ChartSweepView) child;
|
child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void layoutSweep(ChartSweepView sweep) {
|
||||||
|
final Rect parentRect = new Rect(mContent);
|
||||||
|
final Rect childRect = new Rect();
|
||||||
|
|
||||||
|
layoutSweep(sweep, parentRect, childRect);
|
||||||
|
sweep.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void layoutSweep(ChartSweepView sweep, Rect parentRect, Rect childRect) {
|
||||||
final Rect sweepMargins = sweep.getMargins();
|
final Rect sweepMargins = sweep.getMargins();
|
||||||
|
|
||||||
|
// sweep is always placed along specific dimension
|
||||||
if (sweep.getFollowAxis() == ChartSweepView.VERTICAL) {
|
if (sweep.getFollowAxis() == ChartSweepView.VERTICAL) {
|
||||||
parentRect.top += sweepMargins.top + (int) sweep.getPoint();
|
parentRect.top += sweepMargins.top + (int) sweep.getPoint();
|
||||||
parentRect.bottom = parentRect.top;
|
parentRect.bottom = parentRect.top;
|
||||||
parentRect.left += sweepMargins.left;
|
parentRect.left += sweepMargins.left;
|
||||||
parentRect.right += sweepMargins.right;
|
parentRect.right += sweepMargins.right;
|
||||||
Gravity.apply(SWEEP_GRAVITY, parentRect.width(), child.getMeasuredHeight(),
|
Gravity.apply(SWEEP_GRAVITY, parentRect.width(), sweep.getMeasuredHeight(),
|
||||||
parentRect, childRect);
|
parentRect, childRect);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -139,13 +151,9 @@ public class ChartView extends FrameLayout {
|
|||||||
parentRect.right = parentRect.left;
|
parentRect.right = parentRect.left;
|
||||||
parentRect.top += sweepMargins.top;
|
parentRect.top += sweepMargins.top;
|
||||||
parentRect.bottom += sweepMargins.bottom;
|
parentRect.bottom += sweepMargins.bottom;
|
||||||
Gravity.apply(SWEEP_GRAVITY, child.getMeasuredWidth(), parentRect.height(),
|
Gravity.apply(SWEEP_GRAVITY, sweep.getMeasuredWidth(), parentRect.height(),
|
||||||
parentRect, childRect);
|
parentRect, childRect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user