summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2011-09-13 19:56:45 -0700
committerJeff Sharkey <jsharkey@android.com>2011-09-13 20:24:14 -0700
commita53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2 (patch)
treeee3addfdd86cd4fde72ceeb67d290140c64f4b7a
parentec10578884e3c23b3585bccde76e9c12978dda05 (diff)
downloadpackages_apps_Settings-a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2.zip
packages_apps_Settings-a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2.tar.gz
packages_apps_Settings-a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2.tar.bz2
Data usage: precise editing, restrict help, D-pad.
Introduce dialogs for precise editing of network policy warning/limit values, triggered by click on sweep labels. Show up to 999MB before rounding to GB, and round to nearest 5MB value when dragging. Partial D-pad navigation around chart controls. Fix jumping when relayout during drag, and fix sweep overlap bug. When restricting data without limited networks, show dialog help to guide user towards network limit. When reloading chart data, try restoring to nearest cycle. Bug: 5289641, 5111701, 5226078 Change-Id: Ic59dee6496c480a64dc56f8534acf4d81b50bca7
-rw-r--r--res/drawable/data_usage_sweep_background.xml26
-rw-r--r--res/layout/data_usage_bytes_editor.xml40
-rw-r--r--res/layout/data_usage_chart.xml34
-rw-r--r--res/values/strings.xml7
-rw-r--r--src/com/android/settings/DataUsageSummary.java308
-rw-r--r--src/com/android/settings/net/NetworkPolicyEditor.java12
-rw-r--r--src/com/android/settings/widget/ChartDataUsageView.java30
-rw-r--r--src/com/android/settings/widget/ChartSweepView.java113
8 files changed, 492 insertions, 78 deletions
diff --git a/res/drawable/data_usage_sweep_background.xml b/res/drawable/data_usage_sweep_background.xml
new file mode 100644
index 0000000..86f1d09
--- /dev/null
+++ b/res/drawable/data_usage_sweep_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:exitFadeDuration="@android:integer/config_mediumAnimTime">
+
+ <item android:state_window_focused="false" android:drawable="@android:color/transparent" />
+
+ <item android:state_focused="true" android:state_enabled="false" android:drawable="@*android:drawable/list_selector_background_disabled" />
+ <item android:state_focused="true" android:drawable="@*android:drawable/list_selector_background_focused" />
+ <item android:drawable="@android:color/transparent" />
+
+</selector>
diff --git a/res/layout/data_usage_bytes_editor.xml b/res/layout/data_usage_bytes_editor.xml
new file mode 100644
index 0000000..6207391b
--- /dev/null
+++ b/res/layout/data_usage_bytes_editor.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <NumberPicker
+ android:id="@+id/bytes"
+ android:layout_width="48dip"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginLeft="16dip"
+ android:layout_marginRight="16dip"
+ android:focusable="true"
+ android:focusableInTouchMode="true" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:text="@*android:string/megabyteShort" />
+
+</LinearLayout>
diff --git a/res/layout/data_usage_chart.xml b/res/layout/data_usage_chart.xml
index a1a7ec4..38f1c11 100644
--- a/res/layout/data_usage_chart.xml
+++ b/res/layout/data_usage_chart.xml
@@ -59,25 +59,10 @@
settings:fillColorSecondary="#60ba7f3e" />
<com.android.settings.widget.ChartSweepView
- android:id="@+id/sweep_left"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- settings:sweepDrawable="@drawable/data_sweep_left"
- settings:followAxis="horizontal"
- settings:neighborMargin="5dip" />
-
- <com.android.settings.widget.ChartSweepView
- android:id="@+id/sweep_right"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- settings:sweepDrawable="@drawable/data_sweep_right"
- settings:followAxis="horizontal"
- settings:neighborMargin="5dip" />
-
- <com.android.settings.widget.ChartSweepView
android:id="@+id/sweep_warning"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:nextFocusUp="@+id/sweep_limit"
settings:sweepDrawable="@drawable/data_sweep_warning"
settings:followAxis="vertical"
settings:neighborMargin="5dip"
@@ -89,6 +74,7 @@
android:id="@+id/sweep_limit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:nextFocusDown="@+id/sweep_warning"
settings:sweepDrawable="@drawable/data_sweep_limit"
settings:followAxis="vertical"
settings:neighborMargin="5dip"
@@ -96,4 +82,20 @@
settings:labelTemplate="@string/data_usage_sweep_limit"
settings:labelColor="#c01a2c" />
+ <com.android.settings.widget.ChartSweepView
+ android:id="@+id/sweep_left"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ settings:sweepDrawable="@drawable/data_sweep_left"
+ settings:followAxis="horizontal"
+ settings:neighborMargin="5dip" />
+
+ <com.android.settings.widget.ChartSweepView
+ android:id="@+id/sweep_right"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ settings:sweepDrawable="@drawable/data_sweep_right"
+ settings:followAxis="horizontal"
+ settings:neighborMargin="5dip" />
+
</com.android.settings.widget.ChartDataUsageView>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c59ba49..00f19f1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3526,6 +3526,8 @@ found in the list of installed applications.</string>
<string name="data_usage_app_restrict_dialog_title">Restrict background data?</string>
<!-- Body of dialog shown when user restricts background data usage of a specific application. [CHAR LIMIT=NONE] -->
<string name="data_usage_app_restrict_dialog">This feature may negatively impact applications which depend on background data usage.\n\nMore appropriate data usage controls may be found within this application\'s settings.</string>
+ <!-- Body of dialog shown when user attempts to restrict background data before a network data limit has been set. [CHAR LIMIT=NONE] -->
+ <string name="data_usage_restrict_denied_dialog">Restricting background data is only available when you\'ve set a network data limit.</string>
<!-- Title of dialog for editing data usage cycle reset date. [CHAR LIMIT=48] -->
<string name="data_usage_cycle_editor_title">Usage cycle reset date</string>
@@ -3534,6 +3536,11 @@ found in the list of installed applications.</string>
<!-- Positive button title for data usage cycle editor, confirming that changes should be saved. [CHAR LIMIT=32] -->
<string name="data_usage_cycle_editor_positive">Set</string>
+ <!-- Title of dialog for editing data usage warning in bytes. [CHAR LIMIT=48] -->
+ <string name="data_usage_warning_editor_title">Set data usage warning</string>
+ <!-- Title of dialog for editing data usage limit in bytes. [CHAR LIMIT=48] -->
+ <string name="data_usage_limit_editor_title">Set data usage limit</string>
+
<!-- Title of dialog shown before user limits data usage. [CHAR LIMIT=48] -->
<string name="data_usage_limit_dialog_title">Limiting data usage</string>
<!-- Body of dialog shown before user limits mobile data usage. [CHAR LIMIT=NONE] -->
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
index 0e08075..bbc3606 100644
--- a/src/com/android/settings/DataUsageSummary.java
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -20,6 +20,7 @@ import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIMAX;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
+import static android.net.NetworkPolicy.WARNING_DISABLED;
import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
@@ -129,6 +130,7 @@ import com.google.android.collect.Lists;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import java.util.Locale;
import libcore.util.Objects;
@@ -156,7 +158,10 @@ public class DataUsageSummary extends Fragment {
private static final String TAG_CONFIRM_DATA_ROAMING = "confirmDataRoaming";
private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
private static final String TAG_CYCLE_EDITOR = "cycleEditor";
+ private static final String TAG_WARNING_EDITOR = "warningEditor";
+ private static final String TAG_LIMIT_EDITOR = "limitEditor";
private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict";
+ private static final String TAG_DENIED_RESTRICT = "deniedRestrict";
private static final String TAG_CONFIRM_APP_RESTRICT = "confirmAppRestrict";
private static final String TAG_APP_DETAILS = "appDetails";
@@ -295,7 +300,10 @@ public class DataUsageSummary extends Fragment {
mTabHost.setOnTabChangedListener(mTabListener);
mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false);
- mListView.addHeaderView(mHeader, null, false);
+ mHeader.setClickable(true);
+
+ mListView.addHeaderView(mHeader, null, true);
+ mListView.setItemsCanFocus(true);
if (mInsetSide > 0) {
// inset selector and divider drawables
@@ -316,7 +324,10 @@ public class DataUsageSummary extends Fragment {
mDisableAtLimit = new CheckBox(inflater.getContext());
mDisableAtLimit.setClickable(false);
+ mDisableAtLimit.setFocusable(false);
mDisableAtLimitView = inflatePreference(inflater, mNetworkSwitches, mDisableAtLimit);
+ mDisableAtLimitView.setClickable(true);
+ mDisableAtLimitView.setFocusable(true);
mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
mNetworkSwitches.addView(mDisableAtLimitView);
}
@@ -346,7 +357,10 @@ public class DataUsageSummary extends Fragment {
mAppRestrict = new CheckBox(inflater.getContext());
mAppRestrict.setClickable(false);
+ mAppRestrict.setFocusable(false);
mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict);
+ mAppRestrictView.setClickable(true);
+ mAppRestrictView.setFocusable(true);
mAppRestrictView.setOnClickListener(mAppRestrictListener);
mAppSwitches.addView(mAppRestrictView);
}
@@ -456,7 +470,11 @@ public class DataUsageSummary extends Fragment {
case R.id.data_usage_menu_restrict_background: {
final boolean restrictBackground = !item.isChecked();
if (restrictBackground) {
- ConfirmRestrictFragment.show(this);
+ if (hasLimitedNetworks()) {
+ ConfirmRestrictFragment.show(this);
+ } else {
+ DeniedRestrictFragment.show(this);
+ }
} else {
// no confirmation to drop restriction
setRestrictBackground(false);
@@ -619,7 +637,6 @@ public class DataUsageSummary extends Fragment {
if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
mDataEnabledView.setVisibility(View.VISIBLE);
- mDisableAtLimitView.setVisibility(View.VISIBLE);
if (TAB_MOBILE.equals(currentTab)) {
setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile);
@@ -735,7 +752,7 @@ public class DataUsageSummary extends Fragment {
setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
setPreferenceSummary(mAppRestrictView,
getString(R.string.data_usage_app_restrict_background_summary,
- buildLimitedNetworksList()));
+ buildLimitedNetworksString()));
mAppRestrictView.setVisibility(View.VISIBLE);
mAppRestrict.setChecked(getAppRestrictBackground());
@@ -745,12 +762,6 @@ public class DataUsageSummary extends Fragment {
}
}
- private void setPolicyCycleDay(int cycleDay) {
- if (LOGD) Log.d(TAG, "setPolicyCycleDay()");
- mPolicyEditor.setPolicyCycleDay(mTemplate, cycleDay);
- updatePolicy(true);
- }
-
private void setPolicyWarningBytes(long warningBytes) {
if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
mPolicyEditor.setPolicyWarningBytes(mTemplate, warningBytes);
@@ -863,15 +874,8 @@ public class DataUsageSummary extends Fragment {
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);
-
- // when heading back to summary without cycle refresh, kick details
- // update to repopulate list.
- if (!refreshCycle) {
- updateDetailData();
- }
}
// TODO: move enabled state directly into policy
@@ -907,6 +911,8 @@ public class DataUsageSummary extends Fragment {
* item, updating the inspection range on {@link #mChart}.
*/
private void updateCycleList(NetworkPolicy policy) {
+ // stash away currently selected cycle to try restoring below
+ final CycleItem previousItem = (CycleItem) mCycleSpinner.getSelectedItem();
mCycleAdapter.clear();
final Context context = mCycleSpinner.getContext();
@@ -954,8 +960,18 @@ public class DataUsageSummary extends Fragment {
// force pick the current cycle (first item)
if (mCycleAdapter.getCount() > 0) {
- mCycleSpinner.setSelection(0);
- mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
+ final int position = mCycleAdapter.findNearestPosition(previousItem);
+ mCycleSpinner.setSelection(position);
+
+ // only force-update cycle when changed; skipping preserves any
+ // user-defined inspection region.
+ final CycleItem selectedItem = mCycleAdapter.getItem(position);
+ if (!Objects.equal(selectedItem, previousItem)) {
+ mCycleListener.onItemSelected(mCycleSpinner, null, position, 0);
+ } else {
+ // but still kick off loader for detailed list
+ updateDetailData();
+ }
} else {
updateDetailData();
}
@@ -1002,9 +1018,16 @@ public class DataUsageSummary extends Fragment {
final boolean restrictBackground = !mAppRestrict.isChecked();
if (restrictBackground) {
- // enabling restriction; show confirmation dialog which
- // eventually calls setRestrictBackground() once user confirms.
- ConfirmAppRestrictFragment.show(DataUsageSummary.this);
+ if (hasLimitedNetworks()) {
+ // enabling restriction; show confirmation dialog which
+ // eventually calls setRestrictBackground() once user
+ // confirms.
+ ConfirmAppRestrictFragment.show(DataUsageSummary.this);
+ } else {
+ // no limited networks; show dialog to guide user towards
+ // setting a network limit. doesn't mutate restrict state.
+ DeniedRestrictFragment.show(DataUsageSummary.this);
+ }
} else {
setAppRestrictBackground(false);
}
@@ -1211,13 +1234,22 @@ public class DataUsageSummary extends Fragment {
public void onLimitChanged() {
setPolicyLimitBytes(mChart.getLimitBytes());
}
- };
+ /** {@inheritDoc} */
+ public void requestWarningEdit() {
+ WarningEditorFragment.show(DataUsageSummary.this);
+ }
+
+ /** {@inheritDoc} */
+ public void requestLimitEdit() {
+ LimitEditorFragment.show(DataUsageSummary.this);
+ }
+ };
/**
* List item that reflects a specific data usage cycle.
*/
- public static class CycleItem {
+ public static class CycleItem implements Comparable<CycleItem> {
public CharSequence label;
public long start;
public long end;
@@ -1236,6 +1268,20 @@ public class DataUsageSummary extends Fragment {
public String toString() {
return label.toString();
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof CycleItem) {
+ final CycleItem another = (CycleItem) o;
+ return start == another.start && end == another.end;
+ }
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public int compareTo(CycleItem another) {
+ return Long.compare(start, another.start);
+ }
}
private static final StringBuilder sBuilder = new StringBuilder(50);
@@ -1291,6 +1337,25 @@ public class DataUsageSummary extends Fragment {
add(mChangeItem);
}
}
+
+ /**
+ * Find position of {@link CycleItem} in this adapter which is nearest
+ * the given {@link CycleItem}.
+ */
+ public int findNearestPosition(CycleItem target) {
+ if (target != null) {
+ final int count = getCount();
+ for (int i = count - 1; i >= 0; i--) {
+ final CycleItem item = getItem(i);
+ if (item instanceof CycleChangeItem) {
+ continue;
+ } else if (item.compareTo(target) >= 0) {
+ return i;
+ }
+ }
+ }
+ return 0;
+ }
}
private static class AppUsageItem implements Comparable<AppUsageItem> {
@@ -1528,12 +1593,11 @@ public class DataUsageSummary extends Fragment {
* Dialog to edit {@link NetworkPolicy#cycleDay}.
*/
public static class CycleEditorFragment extends DialogFragment {
- private static final String EXTRA_CYCLE_DAY = "cycleDay";
+ private static final String EXTRA_TEMPLATE = "template";
public static void show(DataUsageSummary parent) {
- final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate);
final Bundle args = new Bundle();
- args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay);
+ args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
final CycleEditorFragment dialog = new CycleEditorFragment();
dialog.setArguments(args);
@@ -1544,6 +1608,8 @@ public class DataUsageSummary extends Fragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getActivity();
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ final NetworkPolicyEditor editor = target.mPolicyEditor;
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
@@ -1551,11 +1617,12 @@ public class DataUsageSummary extends Fragment {
final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
- final int oldCycleDay = getArguments().getInt(EXTRA_CYCLE_DAY, 1);
+ final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
+ final int cycleDay = editor.getPolicyCycleDay(template);
cycleDayPicker.setMinValue(1);
cycleDayPicker.setMaxValue(31);
- cycleDayPicker.setValue(oldCycleDay);
+ cycleDayPicker.setValue(cycleDay);
cycleDayPicker.setWrapSelectorWheel(true);
builder.setTitle(R.string.data_usage_cycle_editor_title);
@@ -1565,10 +1632,68 @@ public class DataUsageSummary extends Fragment {
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
final int cycleDay = cycleDayPicker.getValue();
- final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
- if (target != null) {
- target.setPolicyCycleDay(cycleDay);
- }
+ editor.setPolicyCycleDay(template, cycleDay);
+ target.updatePolicy(true);
+ }
+ });
+
+ return builder.create();
+ }
+ }
+
+ /**
+ * Dialog to edit {@link NetworkPolicy#warningBytes}.
+ */
+ public static class WarningEditorFragment extends DialogFragment {
+ private static final String EXTRA_TEMPLATE = "template";
+
+ public static void show(DataUsageSummary parent) {
+ final Bundle args = new Bundle();
+ args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
+
+ final WarningEditorFragment dialog = new WarningEditorFragment();
+ dialog.setArguments(args);
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_WARNING_EDITOR);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ final NetworkPolicyEditor editor = target.mPolicyEditor;
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
+
+ final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
+ final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes);
+
+ final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
+ final long warningBytes = editor.getPolicyWarningBytes(template);
+ final long limitBytes = editor.getPolicyLimitBytes(template);
+
+ bytesPicker.setMinValue(0);
+ if (limitBytes != LIMIT_DISABLED) {
+ bytesPicker.setMaxValue((int) (limitBytes / MB_IN_BYTES) - 1);
+ } else {
+ bytesPicker.setMaxValue(Integer.MAX_VALUE);
+ }
+ bytesPicker.setValue((int) (warningBytes / MB_IN_BYTES));
+ bytesPicker.setWrapSelectorWheel(false);
+
+ builder.setTitle(R.string.data_usage_warning_editor_title);
+ builder.setView(view);
+
+ builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ // clear focus to finish pending text edits
+ bytesPicker.clearFocus();
+
+ final long bytes = bytesPicker.getValue() * MB_IN_BYTES;
+ editor.setPolicyWarningBytes(template, bytes);
+ target.updatePolicy(false);
}
});
@@ -1577,6 +1702,65 @@ public class DataUsageSummary extends Fragment {
}
/**
+ * Dialog to edit {@link NetworkPolicy#limitBytes}.
+ */
+ public static class LimitEditorFragment extends DialogFragment {
+ private static final String EXTRA_TEMPLATE = "template";
+
+ public static void show(DataUsageSummary parent) {
+ final Bundle args = new Bundle();
+ args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
+
+ final LimitEditorFragment dialog = new LimitEditorFragment();
+ dialog.setArguments(args);
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_LIMIT_EDITOR);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ final NetworkPolicyEditor editor = target.mPolicyEditor;
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
+
+ final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
+ final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes);
+
+ final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
+ final long warningBytes = editor.getPolicyWarningBytes(template);
+ final long limitBytes = editor.getPolicyLimitBytes(template);
+
+ bytesPicker.setMaxValue(Integer.MAX_VALUE);
+ if (warningBytes != WARNING_DISABLED) {
+ bytesPicker.setMinValue((int) (warningBytes / MB_IN_BYTES) + 1);
+ } else {
+ bytesPicker.setMinValue(0);
+ }
+ bytesPicker.setValue((int) (limitBytes / MB_IN_BYTES));
+ bytesPicker.setWrapSelectorWheel(false);
+
+ builder.setTitle(R.string.data_usage_limit_editor_title);
+ builder.setView(view);
+
+ builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ // clear focus to finish pending text edits
+ bytesPicker.clearFocus();
+
+ final long bytes = bytesPicker.getValue() * MB_IN_BYTES;
+ editor.setPolicyLimitBytes(template, bytes);
+ target.updatePolicy(false);
+ }
+ });
+
+ return builder.create();
+ }
+ }
+ /**
* Dialog to request user confirmation before disabling data.
*/
public static class ConfirmDataDisableFragment extends DialogFragment {
@@ -1661,7 +1845,7 @@ public class DataUsageSummary extends Fragment {
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
if (target != null) {
- final CharSequence limitedNetworks = target.buildLimitedNetworksList();
+ final CharSequence limitedNetworks = target.buildLimitedNetworksString();
builder.setMessage(
getString(R.string.data_usage_restrict_background, limitedNetworks));
}
@@ -1681,6 +1865,31 @@ public class DataUsageSummary extends Fragment {
}
/**
+ * Dialog to inform user that {@link #POLICY_REJECT_METERED_BACKGROUND}
+ * change has been denied, usually based on
+ * {@link DataUsageSummary#hasLimitedNetworks()}.
+ */
+ public static class DeniedRestrictFragment extends DialogFragment {
+ public static void show(DataUsageSummary parent) {
+ final DeniedRestrictFragment dialog = new DeniedRestrictFragment();
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_DENIED_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_background);
+ builder.setMessage(R.string.data_usage_restrict_denied_dialog);
+ builder.setPositiveButton(android.R.string.ok, null);
+
+ return builder.create();
+ }
+ }
+
+ /**
* Dialog to request user confirmation before setting
* {@link #POLICY_REJECT_METERED_BACKGROUND}.
*/
@@ -1879,10 +2088,32 @@ public class DataUsageSummary extends Fragment {
}
/**
+ * Test if any networks are currently limited.
+ */
+ private boolean hasLimitedNetworks() {
+ return !buildLimitedNetworksList().isEmpty();
+ }
+
+ /**
* Build string describing currently limited networks, which defines when
* background data is restricted.
*/
- private CharSequence buildLimitedNetworksList() {
+ private CharSequence buildLimitedNetworksString() {
+ final List<CharSequence> limited = buildLimitedNetworksList();
+
+ // handle case where no networks limited
+ if (limited.isEmpty()) {
+ limited.add(getText(R.string.data_usage_list_none));
+ }
+
+ return TextUtils.join(limited);
+ }
+
+ /**
+ * Build list of currently limited networks, which defines when background
+ * data is restricted.
+ */
+ private List<CharSequence> buildLimitedNetworksList() {
final Context context = getActivity();
final String subscriberId = getActiveSubscriberId(context);
@@ -1904,12 +2135,7 @@ public class DataUsageSummary extends Fragment {
limited.add(getText(R.string.data_usage_tab_ethernet));
}
- // handle case where no networks limited
- if (limited.isEmpty()) {
- limited.add(getText(R.string.data_usage_list_none));
- }
-
- return TextUtils.join(limited);
+ return limited;
}
/**
diff --git a/src/com/android/settings/net/NetworkPolicyEditor.java b/src/com/android/settings/net/NetworkPolicyEditor.java
index bb5a2c3..5ba8ca4 100644
--- a/src/com/android/settings/net/NetworkPolicyEditor.java
+++ b/src/com/android/settings/net/NetworkPolicyEditor.java
@@ -149,6 +149,10 @@ public class NetworkPolicyEditor {
template, cycleDay, WARNING_DISABLED, LIMIT_DISABLED, SNOOZE_NEVER);
}
+ public int getPolicyCycleDay(NetworkTemplate template) {
+ return getPolicy(template).cycleDay;
+ }
+
public void setPolicyCycleDay(NetworkTemplate template, int cycleDay) {
final NetworkPolicy policy = getOrCreatePolicy(template);
policy.cycleDay = cycleDay;
@@ -156,6 +160,10 @@ public class NetworkPolicyEditor {
writeAsync();
}
+ public long getPolicyWarningBytes(NetworkTemplate template) {
+ return getPolicy(template).warningBytes;
+ }
+
public void setPolicyWarningBytes(NetworkTemplate template, long warningBytes) {
final NetworkPolicy policy = getOrCreatePolicy(template);
policy.warningBytes = warningBytes;
@@ -163,6 +171,10 @@ public class NetworkPolicyEditor {
writeAsync();
}
+ public long getPolicyLimitBytes(NetworkTemplate template) {
+ return getPolicy(template).limitBytes;
+ }
+
public void setPolicyLimitBytes(NetworkTemplate template, long limitBytes) {
final NetworkPolicy policy = getOrCreatePolicy(template);
policy.limitBytes = limitBytes;
diff --git a/src/com/android/settings/widget/ChartDataUsageView.java b/src/com/android/settings/widget/ChartDataUsageView.java
index e831cc1..43ce97c 100644
--- a/src/com/android/settings/widget/ChartDataUsageView.java
+++ b/src/com/android/settings/widget/ChartDataUsageView.java
@@ -69,6 +69,8 @@ public class ChartDataUsageView extends ChartView {
public void onInspectRangeChanged();
public void onWarningChanged();
public void onLimitChanged();
+ public void requestWarningEdit();
+ public void requestLimitEdit();
}
private DataUsageChartListener mListener;
@@ -123,6 +125,15 @@ public class ChartDataUsageView extends ChartView {
mSweepWarning.addOnSweepListener(mVertListener);
mSweepLimit.addOnSweepListener(mVertListener);
+ mSweepWarning.setDragInterval(5 * MB_IN_BYTES);
+ mSweepLimit.setDragInterval(5 * MB_IN_BYTES);
+
+ // TODO: make time sweeps adjustable through dpad
+ mSweepLeft.setClickable(false);
+ mSweepLeft.setFocusable(false);
+ mSweepRight.setClickable(false);
+ mSweepRight.setFocusable(false);
+
// tell everyone about our axis
mGrid.init(mHoriz, mVert);
mSeries.init(mHoriz, mVert);
@@ -276,6 +287,7 @@ public class ChartDataUsageView extends ChartView {
}
private OnSweepListener mHorizListener = new OnSweepListener() {
+ /** {@inheritDoc} */
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
updatePrimaryRange();
@@ -284,6 +296,11 @@ public class ChartDataUsageView extends ChartView {
mListener.onInspectRangeChanged();
}
}
+
+ /** {@inheritDoc} */
+ public void requestEdit(ChartSweepView sweep) {
+ // ignored
+ }
};
private void sendUpdateAxisDelayed(ChartSweepView sweep, boolean force) {
@@ -298,6 +315,7 @@ public class ChartDataUsageView extends ChartView {
}
private OnSweepListener mVertListener = new OnSweepListener() {
+ /** {@inheritDoc} */
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
if (sweepDone) {
clearUpdateAxisDelayed(sweep);
@@ -313,6 +331,15 @@ public class ChartDataUsageView extends ChartView {
sendUpdateAxisDelayed(sweep, false);
}
}
+
+ /** {@inheritDoc} */
+ public void requestEdit(ChartSweepView sweep) {
+ if (sweep == mSweepWarning && mListener != null) {
+ mListener.requestWarningEdit();
+ } else if (sweep == mSweepLimit && mListener != null) {
+ mListener.requestLimitEdit();
+ }
+ }
};
@Override
@@ -540,7 +567,7 @@ public class ChartDataUsageView extends ChartView {
final CharSequence unit;
final long unitFactor;
- if (value <= 100 * MB_IN_BYTES) {
+ if (value < 1000 * MB_IN_BYTES) {
unit = res.getText(com.android.internal.R.string.megabyteShort);
unitFactor = MB_IN_BYTES;
} else {
@@ -551,6 +578,7 @@ public class ChartDataUsageView extends ChartView {
final double result = (double) value / unitFactor;
final double resultRounded;
final CharSequence size;
+
if (result < 10) {
size = String.format("%.1f", result);
resultRounded = (unitFactor * Math.round(result * 10)) / 10;
diff --git a/src/com/android/settings/widget/ChartSweepView.java b/src/com/android/settings/widget/ChartSweepView.java
index 2190588..22a6478 100644
--- a/src/com/android/settings/widget/ChartSweepView.java
+++ b/src/com/android/settings/widget/ChartSweepView.java
@@ -83,11 +83,22 @@ public class ChartSweepView extends View {
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
+ private int mTouchMode = MODE_NONE;
+
+ private static final int MODE_NONE = 0;
+ private static final int MODE_DRAG = 1;
+ private static final int MODE_LABEL = 2;
+
+ private long mDragInterval = 1;
+
public interface OnSweepListener {
public void onSweep(ChartSweepView sweep, boolean sweepDone);
+ public void requestEdit(ChartSweepView sweep);
}
private OnSweepListener mListener;
+
+ private float mTrackingStart;
private MotionEvent mTracking;
public ChartSweepView(Context context) {
@@ -112,15 +123,28 @@ public class ChartSweepView extends View {
setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0));
setLabelColor(a.getColor(R.styleable.ChartSweepView_labelColor, Color.BLUE));
+ // TODO: moved focused state directly into assets
+ setBackgroundResource(R.drawable.data_usage_sweep_background);
+
mOutlinePaint.setColor(Color.RED);
mOutlinePaint.setStrokeWidth(1f);
mOutlinePaint.setStyle(Style.STROKE);
a.recycle();
+ setClickable(true);
+ setFocusable(true);
+ setOnClickListener(mClickListener);
+
setWillNotDraw(false);
}
+ private OnClickListener mClickListener = new OnClickListener() {
+ public void onClick(View v) {
+ dispatchRequestEdit();
+ }
+ };
+
void init(ChartAxis axis) {
mAxis = Preconditions.checkNotNull(axis, "missing axis");
}
@@ -133,6 +157,10 @@ public class ChartSweepView extends View {
return mMargins;
}
+ public void setDragInterval(long dragInterval) {
+ mDragInterval = dragInterval;
+ }
+
/**
* Return the number of pixels that the "target" area is inset from the
* {@link View} edge, along the current {@link #setFollowAxis(int)}.
@@ -159,9 +187,16 @@ public class ChartSweepView extends View {
}
}
+ private void dispatchRequestEdit() {
+ if (mListener != null) {
+ mListener.requestEdit(this);
+ }
+ }
+
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
+ setFocusable(enabled);
requestLayout();
}
@@ -232,6 +267,7 @@ public class ChartSweepView extends View {
private void invalidateLabel() {
if (mLabelTemplate != null && mAxis != null) {
mLabelValue = mAxis.buildLabel(getResources(), mLabelTemplate, mValue);
+ setContentDescription(mLabelTemplate);
invalidateLabelOffset();
invalidate();
} else {
@@ -369,11 +405,16 @@ public class ChartSweepView extends View {
case MotionEvent.ACTION_DOWN: {
// only start tracking when in sweet spot
- final boolean accept;
+ final boolean acceptDrag;
+ final boolean acceptLabel;
if (mFollowAxis == VERTICAL) {
- accept = event.getX() > getWidth() - (mSweepPadding.right * 8);
+ acceptDrag = event.getX() > getWidth() - (mSweepPadding.right * 8);
+ acceptLabel = mLabelLayout != null ? event.getX() < mLabelLayout.getWidth()
+ : false;
} else {
- accept = event.getY() > getHeight() - (mSweepPadding.bottom * 8);
+ acceptDrag = event.getY() > getHeight() - (mSweepPadding.bottom * 8);
+ acceptLabel = mLabelLayout != null ? event.getY() < mLabelLayout.getHeight()
+ : false;
}
final MotionEvent eventInParent = event.copy();
@@ -385,8 +426,14 @@ public class ChartSweepView extends View {
return false;
}
- if (accept) {
+ if (acceptDrag) {
+ if (mFollowAxis == VERTICAL) {
+ mTrackingStart = getTop() - mMargins.top;
+ } else {
+ mTrackingStart = getLeft() - mMargins.left;
+ }
mTracking = event.copy();
+ mTouchMode = MODE_DRAG;
// starting drag should activate entire chart
if (!parent.isActivated()) {
@@ -394,47 +441,68 @@ public class ChartSweepView extends View {
}
return true;
+ } else if (acceptLabel) {
+ mTouchMode = MODE_LABEL;
+ return true;
} else {
+ mTouchMode = MODE_NONE;
return false;
}
}
case MotionEvent.ACTION_MOVE: {
+ if (mTouchMode == MODE_LABEL) {
+ return true;
+ }
+
getParent().requestDisallowInterceptTouchEvent(true);
// content area of parent
final Rect parentContent = getParentContentRect();
final Rect clampRect = computeClampRect(parentContent);
+ if (clampRect.isEmpty()) return true;
+ long value;
if (mFollowAxis == VERTICAL) {
final float currentTargetY = getTop() - mMargins.top;
- final float requestedTargetY = currentTargetY
+ final float requestedTargetY = mTrackingStart
+ (event.getRawY() - mTracking.getRawY());
final float clampedTargetY = MathUtils.constrain(
requestedTargetY, clampRect.top, clampRect.bottom);
setTranslationY(clampedTargetY - currentTargetY);
- setValue(mAxis.convertToValue(clampedTargetY - parentContent.top));
+ value = mAxis.convertToValue(clampedTargetY - parentContent.top);
} else {
final float currentTargetX = getLeft() - mMargins.left;
- final float requestedTargetX = currentTargetX
+ final float requestedTargetX = mTrackingStart
+ (event.getRawX() - mTracking.getRawX());
final float clampedTargetX = MathUtils.constrain(
requestedTargetX, clampRect.left, clampRect.right);
setTranslationX(clampedTargetX - currentTargetX);
- setValue(mAxis.convertToValue(clampedTargetX - parentContent.left));
+ value = mAxis.convertToValue(clampedTargetX - parentContent.left);
}
+ // round value from drag to nearest increment
+ value -= value % mDragInterval;
+ setValue(value);
+
dispatchOnSweep(false);
return true;
}
case MotionEvent.ACTION_UP: {
- mTracking = null;
- mValue = mLabelValue;
- dispatchOnSweep(true);
- setTranslationX(0);
- setTranslationY(0);
- requestLayout();
+ if (mTouchMode == MODE_LABEL) {
+ performClick();
+ } else if (mTouchMode == MODE_DRAG) {
+ mTrackingStart = 0;
+ mTracking = null;
+ mValue = mLabelValue;
+ dispatchOnSweep(true);
+ setTranslationX(0);
+ setTranslationY(0);
+ requestLayout();
+ }
+
+ mTouchMode = MODE_NONE;
return true;
}
default: {
@@ -501,7 +569,9 @@ public class ChartSweepView extends View {
final Rect dynamicRect = buildClampRect(
parentContent, getValidAfterDynamic(), getValidBeforeDynamic(), mNeighborMargin);
- rect.intersect(dynamicRect);
+ if (!rect.intersect(dynamicRect)) {
+ rect.setEmpty();
+ }
return rect;
}
@@ -587,7 +657,7 @@ public class ChartSweepView extends View {
mContentOffset.bottom -= offset;
mMargins.bottom += offset;
} else {
- final int heightAfter = heightBefore * 3;
+ final int heightAfter = heightBefore * 2;
setMeasuredDimension(widthBefore, heightAfter);
mContentOffset.offset(0, (heightAfter - heightBefore) / 2);
@@ -608,13 +678,11 @@ public class ChartSweepView extends View {
@Override
protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
final int width = getWidth();
final int height = getHeight();
- if (DRAW_OUTLINE) {
- canvas.drawRect(0, 0, width, height, mOutlinePaint);
- }
-
final int labelSize;
if (isEnabled() && mLabelLayout != null) {
final int count = canvas.save();
@@ -637,6 +705,11 @@ public class ChartSweepView extends View {
}
mSweep.draw(canvas);
+
+ if (DRAW_OUTLINE) {
+ mOutlinePaint.setColor(Color.RED);
+ canvas.drawRect(0, 0, width, height, mOutlinePaint);
+ }
}
public static float getLabelTop(ChartSweepView view) {