am a53188fe
: Data usage: precise editing, restrict help, D-pad.
* commit 'a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2': Data usage: precise editing, restrict help, D-pad.
This commit is contained in:
26
res/drawable/data_usage_sweep_background.xml
Normal file
26
res/drawable/data_usage_sweep_background.xml
Normal file
@@ -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>
|
40
res/layout/data_usage_bytes_editor.xml
Normal file
40
res/layout/data_usage_bytes_editor.xml
Normal file
@@ -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>
|
@@ -58,6 +58,30 @@
|
|||||||
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"
|
||||||
|
android:nextFocusUp="@+id/sweep_limit"
|
||||||
|
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"
|
||||||
|
android:nextFocusDown="@+id/sweep_warning"
|
||||||
|
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.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"
|
||||||
@@ -74,26 +98,4 @@
|
|||||||
settings:followAxis="horizontal"
|
settings:followAxis="horizontal"
|
||||||
settings:neighborMargin="5dip" />
|
settings:neighborMargin="5dip" />
|
||||||
|
|
||||||
<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>
|
</com.android.settings.widget.ChartDataUsageView>
|
||||||
|
@@ -3526,6 +3526,8 @@ found in the list of installed applications.</string>
|
|||||||
<string name="data_usage_app_restrict_dialog_title">Restrict background data?</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] -->
|
<!-- 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>
|
<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] -->
|
<!-- 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>
|
<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] -->
|
<!-- 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>
|
<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] -->
|
<!-- Title of dialog shown before user limits data usage. [CHAR LIMIT=48] -->
|
||||||
<string name="data_usage_limit_dialog_title">Limiting data usage</string>
|
<string name="data_usage_limit_dialog_title">Limiting data usage</string>
|
||||||
<!-- Body of dialog shown before user limits mobile data usage. [CHAR LIMIT=NONE] -->
|
<!-- Body of dialog shown before user limits mobile data usage. [CHAR LIMIT=NONE] -->
|
||||||
|
@@ -20,6 +20,7 @@ import static android.net.ConnectivityManager.TYPE_ETHERNET;
|
|||||||
import static android.net.ConnectivityManager.TYPE_MOBILE;
|
import static android.net.ConnectivityManager.TYPE_MOBILE;
|
||||||
import static android.net.ConnectivityManager.TYPE_WIMAX;
|
import static android.net.ConnectivityManager.TYPE_WIMAX;
|
||||||
import static android.net.NetworkPolicy.LIMIT_DISABLED;
|
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.EXTRA_NETWORK_TEMPLATE;
|
||||||
import static android.net.NetworkPolicyManager.POLICY_NONE;
|
import static android.net.NetworkPolicyManager.POLICY_NONE;
|
||||||
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
|
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.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import libcore.util.Objects;
|
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_DATA_ROAMING = "confirmDataRoaming";
|
||||||
private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
|
private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
|
||||||
private static final String TAG_CYCLE_EDITOR = "cycleEditor";
|
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_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_CONFIRM_APP_RESTRICT = "confirmAppRestrict";
|
||||||
private static final String TAG_APP_DETAILS = "appDetails";
|
private static final String TAG_APP_DETAILS = "appDetails";
|
||||||
|
|
||||||
@@ -295,7 +300,10 @@ public class DataUsageSummary extends Fragment {
|
|||||||
mTabHost.setOnTabChangedListener(mTabListener);
|
mTabHost.setOnTabChangedListener(mTabListener);
|
||||||
|
|
||||||
mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false);
|
mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false);
|
||||||
mListView.addHeaderView(mHeader, null, false);
|
mHeader.setClickable(true);
|
||||||
|
|
||||||
|
mListView.addHeaderView(mHeader, null, true);
|
||||||
|
mListView.setItemsCanFocus(true);
|
||||||
|
|
||||||
if (mInsetSide > 0) {
|
if (mInsetSide > 0) {
|
||||||
// inset selector and divider drawables
|
// inset selector and divider drawables
|
||||||
@@ -316,7 +324,10 @@ public class DataUsageSummary extends Fragment {
|
|||||||
|
|
||||||
mDisableAtLimit = new CheckBox(inflater.getContext());
|
mDisableAtLimit = new CheckBox(inflater.getContext());
|
||||||
mDisableAtLimit.setClickable(false);
|
mDisableAtLimit.setClickable(false);
|
||||||
|
mDisableAtLimit.setFocusable(false);
|
||||||
mDisableAtLimitView = inflatePreference(inflater, mNetworkSwitches, mDisableAtLimit);
|
mDisableAtLimitView = inflatePreference(inflater, mNetworkSwitches, mDisableAtLimit);
|
||||||
|
mDisableAtLimitView.setClickable(true);
|
||||||
|
mDisableAtLimitView.setFocusable(true);
|
||||||
mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
|
mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
|
||||||
mNetworkSwitches.addView(mDisableAtLimitView);
|
mNetworkSwitches.addView(mDisableAtLimitView);
|
||||||
}
|
}
|
||||||
@@ -346,7 +357,10 @@ public class DataUsageSummary extends Fragment {
|
|||||||
|
|
||||||
mAppRestrict = new CheckBox(inflater.getContext());
|
mAppRestrict = new CheckBox(inflater.getContext());
|
||||||
mAppRestrict.setClickable(false);
|
mAppRestrict.setClickable(false);
|
||||||
|
mAppRestrict.setFocusable(false);
|
||||||
mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict);
|
mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict);
|
||||||
|
mAppRestrictView.setClickable(true);
|
||||||
|
mAppRestrictView.setFocusable(true);
|
||||||
mAppRestrictView.setOnClickListener(mAppRestrictListener);
|
mAppRestrictView.setOnClickListener(mAppRestrictListener);
|
||||||
mAppSwitches.addView(mAppRestrictView);
|
mAppSwitches.addView(mAppRestrictView);
|
||||||
}
|
}
|
||||||
@@ -456,7 +470,11 @@ public class DataUsageSummary extends Fragment {
|
|||||||
case R.id.data_usage_menu_restrict_background: {
|
case R.id.data_usage_menu_restrict_background: {
|
||||||
final boolean restrictBackground = !item.isChecked();
|
final boolean restrictBackground = !item.isChecked();
|
||||||
if (restrictBackground) {
|
if (restrictBackground) {
|
||||||
ConfirmRestrictFragment.show(this);
|
if (hasLimitedNetworks()) {
|
||||||
|
ConfirmRestrictFragment.show(this);
|
||||||
|
} else {
|
||||||
|
DeniedRestrictFragment.show(this);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// no confirmation to drop restriction
|
// no confirmation to drop restriction
|
||||||
setRestrictBackground(false);
|
setRestrictBackground(false);
|
||||||
@@ -619,7 +637,6 @@ public class DataUsageSummary extends Fragment {
|
|||||||
if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
|
if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
|
||||||
|
|
||||||
mDataEnabledView.setVisibility(View.VISIBLE);
|
mDataEnabledView.setVisibility(View.VISIBLE);
|
||||||
mDisableAtLimitView.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
if (TAB_MOBILE.equals(currentTab)) {
|
if (TAB_MOBILE.equals(currentTab)) {
|
||||||
setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile);
|
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);
|
setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
|
||||||
setPreferenceSummary(mAppRestrictView,
|
setPreferenceSummary(mAppRestrictView,
|
||||||
getString(R.string.data_usage_app_restrict_background_summary,
|
getString(R.string.data_usage_app_restrict_background_summary,
|
||||||
buildLimitedNetworksList()));
|
buildLimitedNetworksString()));
|
||||||
|
|
||||||
mAppRestrictView.setVisibility(View.VISIBLE);
|
mAppRestrictView.setVisibility(View.VISIBLE);
|
||||||
mAppRestrict.setChecked(getAppRestrictBackground());
|
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) {
|
private void setPolicyWarningBytes(long warningBytes) {
|
||||||
if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
|
if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
|
||||||
mPolicyEditor.setPolicyWarningBytes(mTemplate, warningBytes);
|
mPolicyEditor.setPolicyWarningBytes(mTemplate, warningBytes);
|
||||||
@@ -863,15 +874,8 @@ public class DataUsageSummary extends Fragment {
|
|||||||
private void updatePolicy(boolean refreshCycle) {
|
private void updatePolicy(boolean refreshCycle) {
|
||||||
if (isAppDetailMode()) {
|
if (isAppDetailMode()) {
|
||||||
mNetworkSwitches.setVisibility(View.GONE);
|
mNetworkSwitches.setVisibility(View.GONE);
|
||||||
// we fall through to update cycle list for detail mode
|
|
||||||
} else {
|
} else {
|
||||||
mNetworkSwitches.setVisibility(View.VISIBLE);
|
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
|
// TODO: move enabled state directly into policy
|
||||||
@@ -907,6 +911,8 @@ public class DataUsageSummary extends Fragment {
|
|||||||
* item, updating the inspection range on {@link #mChart}.
|
* item, updating the inspection range on {@link #mChart}.
|
||||||
*/
|
*/
|
||||||
private void updateCycleList(NetworkPolicy policy) {
|
private void updateCycleList(NetworkPolicy policy) {
|
||||||
|
// stash away currently selected cycle to try restoring below
|
||||||
|
final CycleItem previousItem = (CycleItem) mCycleSpinner.getSelectedItem();
|
||||||
mCycleAdapter.clear();
|
mCycleAdapter.clear();
|
||||||
|
|
||||||
final Context context = mCycleSpinner.getContext();
|
final Context context = mCycleSpinner.getContext();
|
||||||
@@ -954,8 +960,18 @@ public class DataUsageSummary extends Fragment {
|
|||||||
|
|
||||||
// force pick the current cycle (first item)
|
// force pick the current cycle (first item)
|
||||||
if (mCycleAdapter.getCount() > 0) {
|
if (mCycleAdapter.getCount() > 0) {
|
||||||
mCycleSpinner.setSelection(0);
|
final int position = mCycleAdapter.findNearestPosition(previousItem);
|
||||||
mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
|
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 {
|
} else {
|
||||||
updateDetailData();
|
updateDetailData();
|
||||||
}
|
}
|
||||||
@@ -1002,9 +1018,16 @@ public class DataUsageSummary extends Fragment {
|
|||||||
final boolean restrictBackground = !mAppRestrict.isChecked();
|
final boolean restrictBackground = !mAppRestrict.isChecked();
|
||||||
|
|
||||||
if (restrictBackground) {
|
if (restrictBackground) {
|
||||||
// enabling restriction; show confirmation dialog which
|
if (hasLimitedNetworks()) {
|
||||||
// eventually calls setRestrictBackground() once user confirms.
|
// enabling restriction; show confirmation dialog which
|
||||||
ConfirmAppRestrictFragment.show(DataUsageSummary.this);
|
// 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 {
|
} else {
|
||||||
setAppRestrictBackground(false);
|
setAppRestrictBackground(false);
|
||||||
}
|
}
|
||||||
@@ -1211,13 +1234,22 @@ public class DataUsageSummary extends Fragment {
|
|||||||
public void onLimitChanged() {
|
public void onLimitChanged() {
|
||||||
setPolicyLimitBytes(mChart.getLimitBytes());
|
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.
|
* List item that reflects a specific data usage cycle.
|
||||||
*/
|
*/
|
||||||
public static class CycleItem {
|
public static class CycleItem implements Comparable<CycleItem> {
|
||||||
public CharSequence label;
|
public CharSequence label;
|
||||||
public long start;
|
public long start;
|
||||||
public long end;
|
public long end;
|
||||||
@@ -1236,6 +1268,20 @@ public class DataUsageSummary extends Fragment {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return label.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);
|
private static final StringBuilder sBuilder = new StringBuilder(50);
|
||||||
@@ -1291,6 +1337,25 @@ public class DataUsageSummary extends Fragment {
|
|||||||
add(mChangeItem);
|
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> {
|
private static class AppUsageItem implements Comparable<AppUsageItem> {
|
||||||
@@ -1528,12 +1593,11 @@ public class DataUsageSummary extends Fragment {
|
|||||||
* Dialog to edit {@link NetworkPolicy#cycleDay}.
|
* Dialog to edit {@link NetworkPolicy#cycleDay}.
|
||||||
*/
|
*/
|
||||||
public static class CycleEditorFragment extends DialogFragment {
|
public static class CycleEditorFragment extends DialogFragment {
|
||||||
private static final String EXTRA_CYCLE_DAY = "cycleDay";
|
private static final String EXTRA_TEMPLATE = "template";
|
||||||
|
|
||||||
public static void show(DataUsageSummary parent) {
|
public static void show(DataUsageSummary parent) {
|
||||||
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.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
|
||||||
|
|
||||||
final CycleEditorFragment dialog = new CycleEditorFragment();
|
final CycleEditorFragment dialog = new CycleEditorFragment();
|
||||||
dialog.setArguments(args);
|
dialog.setArguments(args);
|
||||||
@@ -1544,6 +1608,8 @@ public class DataUsageSummary extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
final Context context = getActivity();
|
final Context context = getActivity();
|
||||||
|
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
|
||||||
|
final NetworkPolicyEditor editor = target.mPolicyEditor;
|
||||||
|
|
||||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||||
final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
|
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 View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
|
||||||
final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
|
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.setMinValue(1);
|
||||||
cycleDayPicker.setMaxValue(31);
|
cycleDayPicker.setMaxValue(31);
|
||||||
cycleDayPicker.setValue(oldCycleDay);
|
cycleDayPicker.setValue(cycleDay);
|
||||||
cycleDayPicker.setWrapSelectorWheel(true);
|
cycleDayPicker.setWrapSelectorWheel(true);
|
||||||
|
|
||||||
builder.setTitle(R.string.data_usage_cycle_editor_title);
|
builder.setTitle(R.string.data_usage_cycle_editor_title);
|
||||||
@@ -1565,10 +1632,8 @@ public class DataUsageSummary extends Fragment {
|
|||||||
new DialogInterface.OnClickListener() {
|
new DialogInterface.OnClickListener() {
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
final int cycleDay = cycleDayPicker.getValue();
|
final int cycleDay = cycleDayPicker.getValue();
|
||||||
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
|
editor.setPolicyCycleDay(template, cycleDay);
|
||||||
if (target != null) {
|
target.updatePolicy(true);
|
||||||
target.setPolicyCycleDay(cycleDay);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1576,6 +1641,125 @@ public class DataUsageSummary extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* Dialog to request user confirmation before disabling data.
|
||||||
*/
|
*/
|
||||||
@@ -1661,7 +1845,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
|
|
||||||
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
|
final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
final CharSequence limitedNetworks = target.buildLimitedNetworksList();
|
final CharSequence limitedNetworks = target.buildLimitedNetworksString();
|
||||||
builder.setMessage(
|
builder.setMessage(
|
||||||
getString(R.string.data_usage_restrict_background, limitedNetworks));
|
getString(R.string.data_usage_restrict_background, limitedNetworks));
|
||||||
}
|
}
|
||||||
@@ -1680,6 +1864,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
|
* Dialog to request user confirmation before setting
|
||||||
* {@link #POLICY_REJECT_METERED_BACKGROUND}.
|
* {@link #POLICY_REJECT_METERED_BACKGROUND}.
|
||||||
@@ -1878,11 +2087,33 @@ public class DataUsageSummary extends Fragment {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if any networks are currently limited.
|
||||||
|
*/
|
||||||
|
private boolean hasLimitedNetworks() {
|
||||||
|
return !buildLimitedNetworksList().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build string describing currently limited networks, which defines when
|
* Build string describing currently limited networks, which defines when
|
||||||
* background data is restricted.
|
* 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 Context context = getActivity();
|
||||||
final String subscriberId = getActiveSubscriberId(context);
|
final String subscriberId = getActiveSubscriberId(context);
|
||||||
|
|
||||||
@@ -1904,12 +2135,7 @@ public class DataUsageSummary extends Fragment {
|
|||||||
limited.add(getText(R.string.data_usage_tab_ethernet));
|
limited.add(getText(R.string.data_usage_tab_ethernet));
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle case where no networks limited
|
return limited;
|
||||||
if (limited.isEmpty()) {
|
|
||||||
limited.add(getText(R.string.data_usage_list_none));
|
|
||||||
}
|
|
||||||
|
|
||||||
return TextUtils.join(limited);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -149,6 +149,10 @@ public class NetworkPolicyEditor {
|
|||||||
template, cycleDay, WARNING_DISABLED, LIMIT_DISABLED, SNOOZE_NEVER);
|
template, cycleDay, WARNING_DISABLED, LIMIT_DISABLED, SNOOZE_NEVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getPolicyCycleDay(NetworkTemplate template) {
|
||||||
|
return getPolicy(template).cycleDay;
|
||||||
|
}
|
||||||
|
|
||||||
public void setPolicyCycleDay(NetworkTemplate template, int cycleDay) {
|
public void setPolicyCycleDay(NetworkTemplate template, int cycleDay) {
|
||||||
final NetworkPolicy policy = getOrCreatePolicy(template);
|
final NetworkPolicy policy = getOrCreatePolicy(template);
|
||||||
policy.cycleDay = cycleDay;
|
policy.cycleDay = cycleDay;
|
||||||
@@ -156,6 +160,10 @@ public class NetworkPolicyEditor {
|
|||||||
writeAsync();
|
writeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getPolicyWarningBytes(NetworkTemplate template) {
|
||||||
|
return getPolicy(template).warningBytes;
|
||||||
|
}
|
||||||
|
|
||||||
public void setPolicyWarningBytes(NetworkTemplate template, long warningBytes) {
|
public void setPolicyWarningBytes(NetworkTemplate template, long warningBytes) {
|
||||||
final NetworkPolicy policy = getOrCreatePolicy(template);
|
final NetworkPolicy policy = getOrCreatePolicy(template);
|
||||||
policy.warningBytes = warningBytes;
|
policy.warningBytes = warningBytes;
|
||||||
@@ -163,6 +171,10 @@ public class NetworkPolicyEditor {
|
|||||||
writeAsync();
|
writeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getPolicyLimitBytes(NetworkTemplate template) {
|
||||||
|
return getPolicy(template).limitBytes;
|
||||||
|
}
|
||||||
|
|
||||||
public void setPolicyLimitBytes(NetworkTemplate template, long limitBytes) {
|
public void setPolicyLimitBytes(NetworkTemplate template, long limitBytes) {
|
||||||
final NetworkPolicy policy = getOrCreatePolicy(template);
|
final NetworkPolicy policy = getOrCreatePolicy(template);
|
||||||
policy.limitBytes = limitBytes;
|
policy.limitBytes = limitBytes;
|
||||||
|
@@ -69,6 +69,8 @@ public class ChartDataUsageView extends ChartView {
|
|||||||
public void onInspectRangeChanged();
|
public void onInspectRangeChanged();
|
||||||
public void onWarningChanged();
|
public void onWarningChanged();
|
||||||
public void onLimitChanged();
|
public void onLimitChanged();
|
||||||
|
public void requestWarningEdit();
|
||||||
|
public void requestLimitEdit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private DataUsageChartListener mListener;
|
private DataUsageChartListener mListener;
|
||||||
@@ -123,6 +125,15 @@ public class ChartDataUsageView extends ChartView {
|
|||||||
mSweepWarning.addOnSweepListener(mVertListener);
|
mSweepWarning.addOnSweepListener(mVertListener);
|
||||||
mSweepLimit.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
|
// tell everyone about our axis
|
||||||
mGrid.init(mHoriz, mVert);
|
mGrid.init(mHoriz, mVert);
|
||||||
mSeries.init(mHoriz, mVert);
|
mSeries.init(mHoriz, mVert);
|
||||||
@@ -276,6 +287,7 @@ public class ChartDataUsageView extends ChartView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private OnSweepListener mHorizListener = new OnSweepListener() {
|
private OnSweepListener mHorizListener = new OnSweepListener() {
|
||||||
|
/** {@inheritDoc} */
|
||||||
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
|
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
|
||||||
updatePrimaryRange();
|
updatePrimaryRange();
|
||||||
|
|
||||||
@@ -284,6 +296,11 @@ public class ChartDataUsageView extends ChartView {
|
|||||||
mListener.onInspectRangeChanged();
|
mListener.onInspectRangeChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public void requestEdit(ChartSweepView sweep) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private void sendUpdateAxisDelayed(ChartSweepView sweep, boolean force) {
|
private void sendUpdateAxisDelayed(ChartSweepView sweep, boolean force) {
|
||||||
@@ -298,6 +315,7 @@ public class ChartDataUsageView extends ChartView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private OnSweepListener mVertListener = new OnSweepListener() {
|
private OnSweepListener mVertListener = new OnSweepListener() {
|
||||||
|
/** {@inheritDoc} */
|
||||||
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
|
public void onSweep(ChartSweepView sweep, boolean sweepDone) {
|
||||||
if (sweepDone) {
|
if (sweepDone) {
|
||||||
clearUpdateAxisDelayed(sweep);
|
clearUpdateAxisDelayed(sweep);
|
||||||
@@ -313,6 +331,15 @@ public class ChartDataUsageView extends ChartView {
|
|||||||
sendUpdateAxisDelayed(sweep, false);
|
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
|
@Override
|
||||||
@@ -540,7 +567,7 @@ public class ChartDataUsageView extends ChartView {
|
|||||||
|
|
||||||
final CharSequence unit;
|
final CharSequence unit;
|
||||||
final long unitFactor;
|
final long unitFactor;
|
||||||
if (value <= 100 * MB_IN_BYTES) {
|
if (value < 1000 * MB_IN_BYTES) {
|
||||||
unit = res.getText(com.android.internal.R.string.megabyteShort);
|
unit = res.getText(com.android.internal.R.string.megabyteShort);
|
||||||
unitFactor = MB_IN_BYTES;
|
unitFactor = MB_IN_BYTES;
|
||||||
} else {
|
} else {
|
||||||
@@ -551,6 +578,7 @@ public class ChartDataUsageView extends ChartView {
|
|||||||
final double result = (double) value / unitFactor;
|
final double result = (double) value / unitFactor;
|
||||||
final double resultRounded;
|
final double resultRounded;
|
||||||
final CharSequence size;
|
final CharSequence size;
|
||||||
|
|
||||||
if (result < 10) {
|
if (result < 10) {
|
||||||
size = String.format("%.1f", result);
|
size = String.format("%.1f", result);
|
||||||
resultRounded = (unitFactor * Math.round(result * 10)) / 10;
|
resultRounded = (unitFactor * Math.round(result * 10)) / 10;
|
||||||
|
@@ -83,11 +83,22 @@ public class ChartSweepView extends View {
|
|||||||
public static final int HORIZONTAL = 0;
|
public static final int HORIZONTAL = 0;
|
||||||
public static final int VERTICAL = 1;
|
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 interface OnSweepListener {
|
||||||
public void onSweep(ChartSweepView sweep, boolean sweepDone);
|
public void onSweep(ChartSweepView sweep, boolean sweepDone);
|
||||||
|
public void requestEdit(ChartSweepView sweep);
|
||||||
}
|
}
|
||||||
|
|
||||||
private OnSweepListener mListener;
|
private OnSweepListener mListener;
|
||||||
|
|
||||||
|
private float mTrackingStart;
|
||||||
private MotionEvent mTracking;
|
private MotionEvent mTracking;
|
||||||
|
|
||||||
public ChartSweepView(Context context) {
|
public ChartSweepView(Context context) {
|
||||||
@@ -112,15 +123,28 @@ 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));
|
||||||
|
|
||||||
|
// TODO: moved focused state directly into assets
|
||||||
|
setBackgroundResource(R.drawable.data_usage_sweep_background);
|
||||||
|
|
||||||
mOutlinePaint.setColor(Color.RED);
|
mOutlinePaint.setColor(Color.RED);
|
||||||
mOutlinePaint.setStrokeWidth(1f);
|
mOutlinePaint.setStrokeWidth(1f);
|
||||||
mOutlinePaint.setStyle(Style.STROKE);
|
mOutlinePaint.setStyle(Style.STROKE);
|
||||||
|
|
||||||
a.recycle();
|
a.recycle();
|
||||||
|
|
||||||
|
setClickable(true);
|
||||||
|
setFocusable(true);
|
||||||
|
setOnClickListener(mClickListener);
|
||||||
|
|
||||||
setWillNotDraw(false);
|
setWillNotDraw(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private OnClickListener mClickListener = new OnClickListener() {
|
||||||
|
public void onClick(View v) {
|
||||||
|
dispatchRequestEdit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
void init(ChartAxis axis) {
|
void init(ChartAxis axis) {
|
||||||
mAxis = Preconditions.checkNotNull(axis, "missing axis");
|
mAxis = Preconditions.checkNotNull(axis, "missing axis");
|
||||||
}
|
}
|
||||||
@@ -133,6 +157,10 @@ public class ChartSweepView extends View {
|
|||||||
return mMargins;
|
return mMargins;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDragInterval(long dragInterval) {
|
||||||
|
mDragInterval = dragInterval;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the number of pixels that the "target" area is inset from the
|
* Return the number of pixels that the "target" area is inset from the
|
||||||
* {@link View} edge, along the current {@link #setFollowAxis(int)}.
|
* {@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
|
@Override
|
||||||
public void setEnabled(boolean enabled) {
|
public void setEnabled(boolean enabled) {
|
||||||
super.setEnabled(enabled);
|
super.setEnabled(enabled);
|
||||||
|
setFocusable(enabled);
|
||||||
requestLayout();
|
requestLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,6 +267,7 @@ public class ChartSweepView extends View {
|
|||||||
private void invalidateLabel() {
|
private void invalidateLabel() {
|
||||||
if (mLabelTemplate != null && mAxis != null) {
|
if (mLabelTemplate != null && mAxis != null) {
|
||||||
mLabelValue = mAxis.buildLabel(getResources(), mLabelTemplate, mValue);
|
mLabelValue = mAxis.buildLabel(getResources(), mLabelTemplate, mValue);
|
||||||
|
setContentDescription(mLabelTemplate);
|
||||||
invalidateLabelOffset();
|
invalidateLabelOffset();
|
||||||
invalidate();
|
invalidate();
|
||||||
} else {
|
} else {
|
||||||
@@ -369,11 +405,16 @@ public class ChartSweepView extends View {
|
|||||||
case MotionEvent.ACTION_DOWN: {
|
case MotionEvent.ACTION_DOWN: {
|
||||||
|
|
||||||
// only start tracking when in sweet spot
|
// only start tracking when in sweet spot
|
||||||
final boolean accept;
|
final boolean acceptDrag;
|
||||||
|
final boolean acceptLabel;
|
||||||
if (mFollowAxis == VERTICAL) {
|
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 {
|
} 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();
|
final MotionEvent eventInParent = event.copy();
|
||||||
@@ -385,56 +426,83 @@ public class ChartSweepView extends View {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accept) {
|
if (acceptDrag) {
|
||||||
|
if (mFollowAxis == VERTICAL) {
|
||||||
|
mTrackingStart = getTop() - mMargins.top;
|
||||||
|
} else {
|
||||||
|
mTrackingStart = getLeft() - mMargins.left;
|
||||||
|
}
|
||||||
mTracking = event.copy();
|
mTracking = event.copy();
|
||||||
|
mTouchMode = MODE_DRAG;
|
||||||
|
|
||||||
// starting drag should activate entire chart
|
// starting drag should activate entire chart
|
||||||
if (!parent.isActivated()) {
|
if (!parent.isActivated()) {
|
||||||
parent.setActivated(true);
|
parent.setActivated(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (acceptLabel) {
|
||||||
|
mTouchMode = MODE_LABEL;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
mTouchMode = MODE_NONE;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case MotionEvent.ACTION_MOVE: {
|
case MotionEvent.ACTION_MOVE: {
|
||||||
|
if (mTouchMode == MODE_LABEL) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
getParent().requestDisallowInterceptTouchEvent(true);
|
getParent().requestDisallowInterceptTouchEvent(true);
|
||||||
|
|
||||||
// content area of parent
|
// content area of parent
|
||||||
final Rect parentContent = getParentContentRect();
|
final Rect parentContent = getParentContentRect();
|
||||||
final Rect clampRect = computeClampRect(parentContent);
|
final Rect clampRect = computeClampRect(parentContent);
|
||||||
|
if (clampRect.isEmpty()) return true;
|
||||||
|
|
||||||
|
long value;
|
||||||
if (mFollowAxis == VERTICAL) {
|
if (mFollowAxis == VERTICAL) {
|
||||||
final float currentTargetY = getTop() - mMargins.top;
|
final float currentTargetY = getTop() - mMargins.top;
|
||||||
final float requestedTargetY = currentTargetY
|
final float requestedTargetY = mTrackingStart
|
||||||
+ (event.getRawY() - mTracking.getRawY());
|
+ (event.getRawY() - mTracking.getRawY());
|
||||||
final float clampedTargetY = MathUtils.constrain(
|
final float clampedTargetY = MathUtils.constrain(
|
||||||
requestedTargetY, clampRect.top, clampRect.bottom);
|
requestedTargetY, clampRect.top, clampRect.bottom);
|
||||||
setTranslationY(clampedTargetY - currentTargetY);
|
setTranslationY(clampedTargetY - currentTargetY);
|
||||||
|
|
||||||
setValue(mAxis.convertToValue(clampedTargetY - parentContent.top));
|
value = mAxis.convertToValue(clampedTargetY - parentContent.top);
|
||||||
} else {
|
} else {
|
||||||
final float currentTargetX = getLeft() - mMargins.left;
|
final float currentTargetX = getLeft() - mMargins.left;
|
||||||
final float requestedTargetX = currentTargetX
|
final float requestedTargetX = mTrackingStart
|
||||||
+ (event.getRawX() - mTracking.getRawX());
|
+ (event.getRawX() - mTracking.getRawX());
|
||||||
final float clampedTargetX = MathUtils.constrain(
|
final float clampedTargetX = MathUtils.constrain(
|
||||||
requestedTargetX, clampRect.left, clampRect.right);
|
requestedTargetX, clampRect.left, clampRect.right);
|
||||||
setTranslationX(clampedTargetX - currentTargetX);
|
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);
|
dispatchOnSweep(false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case MotionEvent.ACTION_UP: {
|
case MotionEvent.ACTION_UP: {
|
||||||
mTracking = null;
|
if (mTouchMode == MODE_LABEL) {
|
||||||
mValue = mLabelValue;
|
performClick();
|
||||||
dispatchOnSweep(true);
|
} else if (mTouchMode == MODE_DRAG) {
|
||||||
setTranslationX(0);
|
mTrackingStart = 0;
|
||||||
setTranslationY(0);
|
mTracking = null;
|
||||||
requestLayout();
|
mValue = mLabelValue;
|
||||||
|
dispatchOnSweep(true);
|
||||||
|
setTranslationX(0);
|
||||||
|
setTranslationY(0);
|
||||||
|
requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
mTouchMode = MODE_NONE;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@@ -501,7 +569,9 @@ public class ChartSweepView extends View {
|
|||||||
final Rect dynamicRect = buildClampRect(
|
final Rect dynamicRect = buildClampRect(
|
||||||
parentContent, getValidAfterDynamic(), getValidBeforeDynamic(), mNeighborMargin);
|
parentContent, getValidAfterDynamic(), getValidBeforeDynamic(), mNeighborMargin);
|
||||||
|
|
||||||
rect.intersect(dynamicRect);
|
if (!rect.intersect(dynamicRect)) {
|
||||||
|
rect.setEmpty();
|
||||||
|
}
|
||||||
return rect;
|
return rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,7 +657,7 @@ public class ChartSweepView extends View {
|
|||||||
mContentOffset.bottom -= offset;
|
mContentOffset.bottom -= offset;
|
||||||
mMargins.bottom += offset;
|
mMargins.bottom += offset;
|
||||||
} else {
|
} else {
|
||||||
final int heightAfter = heightBefore * 3;
|
final int heightAfter = heightBefore * 2;
|
||||||
setMeasuredDimension(widthBefore, heightAfter);
|
setMeasuredDimension(widthBefore, heightAfter);
|
||||||
mContentOffset.offset(0, (heightAfter - heightBefore) / 2);
|
mContentOffset.offset(0, (heightAfter - heightBefore) / 2);
|
||||||
|
|
||||||
@@ -608,13 +678,11 @@ public class ChartSweepView extends View {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDraw(Canvas canvas) {
|
protected void onDraw(Canvas canvas) {
|
||||||
|
super.onDraw(canvas);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
final int labelSize;
|
final int labelSize;
|
||||||
if (isEnabled() && mLabelLayout != null) {
|
if (isEnabled() && mLabelLayout != null) {
|
||||||
final int count = canvas.save();
|
final int count = canvas.save();
|
||||||
@@ -637,6 +705,11 @@ public class ChartSweepView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mSweep.draw(canvas);
|
mSweep.draw(canvas);
|
||||||
|
|
||||||
|
if (DRAW_OUTLINE) {
|
||||||
|
mOutlinePaint.setColor(Color.RED);
|
||||||
|
canvas.drawRect(0, 0, width, height, mOutlinePaint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float getLabelTop(ChartSweepView view) {
|
public static float getLabelTop(ChartSweepView view) {
|
||||||
|
Reference in New Issue
Block a user