2 * Copyright (C) 2016 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
15 package com.android.settings.datausage;
17 import static android.net.NetworkPolicy.CYCLE_NONE;
18 import static android.net.NetworkPolicy.LIMIT_DISABLED;
19 import static android.net.NetworkPolicy.WARNING_DISABLED;
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.app.Fragment;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.res.Resources;
27 import android.net.NetworkPolicy;
28 import android.net.NetworkTemplate;
29 import android.os.Bundle;
30 import android.support.v14.preference.SwitchPreference;
31 import android.support.v7.preference.Preference;
32 import android.text.format.Time;
33 import android.util.FeatureFlagUtils;
34 import android.util.Log;
35 import android.view.LayoutInflater;
36 import android.view.View;
37 import android.widget.EditText;
38 import android.widget.NumberPicker;
39 import android.widget.Spinner;
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
43 import com.android.settings.R;
44 import com.android.settings.core.FeatureFlags;
45 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
46 import com.android.settingslib.NetworkPolicyEditor;
47 import com.android.settingslib.net.DataUsageController;
49 public class BillingCycleSettings extends DataUsageBase implements
50 Preference.OnPreferenceChangeListener, DataUsageEditController {
52 private static final String TAG = "BillingCycleSettings";
53 private static final boolean LOGD = false;
54 public static final long MIB_IN_BYTES = 1024 * 1024;
55 public static final long GIB_IN_BYTES = MIB_IN_BYTES * 1024;
57 private static final long MAX_DATA_LIMIT_BYTES = 50000 * GIB_IN_BYTES;
59 private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
60 private static final String TAG_CYCLE_EDITOR = "cycleEditor";
61 private static final String TAG_WARNING_EDITOR = "warningEditor";
63 private static final String KEY_BILLING_CYCLE = "billing_cycle";
64 private static final String KEY_SET_DATA_WARNING = "set_data_warning";
65 private static final String KEY_DATA_WARNING = "data_warning";
66 @VisibleForTesting static final String KEY_SET_DATA_LIMIT = "set_data_limit";
67 private static final String KEY_DATA_LIMIT = "data_limit";
69 private NetworkTemplate mNetworkTemplate;
70 private Preference mBillingCycle;
71 private Preference mDataWarning;
72 private SwitchPreference mEnableDataWarning;
73 private SwitchPreference mEnableDataLimit;
74 private Preference mDataLimit;
75 private DataUsageController mDataUsageController;
78 void setUpForTest(NetworkPolicyEditor policyEditor,
79 Preference billingCycle,
81 Preference dataWarning,
82 SwitchPreference enableLimit,
83 SwitchPreference enableWarning) {
84 services.mPolicyEditor = policyEditor;
85 mBillingCycle = billingCycle;
86 mDataLimit = dataLimit;
87 mDataWarning = dataWarning;
88 mEnableDataLimit = enableLimit;
89 mEnableDataWarning = enableWarning;
93 public void onCreate(Bundle icicle) {
94 super.onCreate(icicle);
96 mDataUsageController = new DataUsageController(getContext());
98 Bundle args = getArguments();
99 mNetworkTemplate = args.getParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE);
101 addPreferencesFromResource(R.xml.billing_cycle);
102 mBillingCycle = findPreference(KEY_BILLING_CYCLE);
103 mEnableDataWarning = (SwitchPreference) findPreference(KEY_SET_DATA_WARNING);
104 mEnableDataWarning.setOnPreferenceChangeListener(this);
105 mDataWarning = findPreference(KEY_DATA_WARNING);
106 mEnableDataLimit = (SwitchPreference) findPreference(KEY_SET_DATA_LIMIT);
107 mEnableDataLimit.setOnPreferenceChangeListener(this);
108 mDataLimit = findPreference(KEY_DATA_LIMIT);
110 mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.data_warning_footnote);
114 public void onResume() {
121 final int cycleDay = services.mPolicyEditor.getPolicyCycleDay(mNetworkTemplate);
122 if (FeatureFlagUtils.isEnabled(getContext(), FeatureFlags.DATA_USAGE_SETTINGS_V2)) {
123 mBillingCycle.setSummary(null);
124 } else if (cycleDay != CYCLE_NONE) {
125 mBillingCycle.setSummary(getString(R.string.billing_cycle_fragment_summary, cycleDay));
127 mBillingCycle.setSummary(null);
129 final long warningBytes = services.mPolicyEditor.getPolicyWarningBytes(mNetworkTemplate);
130 if (warningBytes != WARNING_DISABLED) {
131 mDataWarning.setSummary(DataUsageUtils.formatDataUsage(getContext(), warningBytes));
132 mDataWarning.setEnabled(true);
133 mEnableDataWarning.setChecked(true);
135 mDataWarning.setSummary(null);
136 mDataWarning.setEnabled(false);
137 mEnableDataWarning.setChecked(false);
139 final long limitBytes = services.mPolicyEditor.getPolicyLimitBytes(mNetworkTemplate);
140 if (limitBytes != LIMIT_DISABLED) {
141 mDataLimit.setSummary(DataUsageUtils.formatDataUsage(getContext(), limitBytes));
142 mDataLimit.setEnabled(true);
143 mEnableDataLimit.setChecked(true);
145 mDataLimit.setSummary(null);
146 mDataLimit.setEnabled(false);
147 mEnableDataLimit.setChecked(false);
152 public boolean onPreferenceTreeClick(Preference preference) {
153 if (preference == mBillingCycle) {
154 CycleEditorFragment.show(this);
156 } else if (preference == mDataWarning) {
157 BytesEditorFragment.show(this, false);
159 } else if (preference == mDataLimit) {
160 BytesEditorFragment.show(this, true);
163 return super.onPreferenceTreeClick(preference);
167 public boolean onPreferenceChange(Preference preference, Object newValue) {
168 if (mEnableDataLimit == preference) {
169 boolean enabled = (Boolean) newValue;
171 setPolicyLimitBytes(LIMIT_DISABLED);
174 ConfirmLimitFragment.show(this);
175 // This preference is enabled / disabled by ConfirmLimitFragment.
177 } else if (mEnableDataWarning == preference) {
178 boolean enabled = (Boolean) newValue;
180 setPolicyWarningBytes(mDataUsageController.getDefaultWarningLevel());
182 setPolicyWarningBytes(WARNING_DISABLED);
190 public int getMetricsCategory() {
191 return MetricsEvent.BILLING_CYCLE;
195 void setPolicyLimitBytes(long limitBytes) {
196 if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
197 services.mPolicyEditor.setPolicyLimitBytes(mNetworkTemplate, limitBytes);
201 private void setPolicyWarningBytes(long warningBytes) {
202 if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
203 services.mPolicyEditor.setPolicyWarningBytes(mNetworkTemplate, warningBytes);
208 public NetworkPolicyEditor getNetworkPolicyEditor() {
209 return services.mPolicyEditor;
213 public NetworkTemplate getNetworkTemplate() {
214 return mNetworkTemplate;
218 public void updateDataUsage() {
223 * Dialog to edit {@link NetworkPolicy#warningBytes}.
225 public static class BytesEditorFragment extends InstrumentedDialogFragment
226 implements DialogInterface.OnClickListener {
227 private static final String EXTRA_TEMPLATE = "template";
228 private static final String EXTRA_LIMIT = "limit";
231 public static void show(DataUsageEditController parent, boolean isLimit) {
232 if (!(parent instanceof Fragment)) {
235 Fragment targetFragment = (Fragment) parent;
236 if (!targetFragment.isAdded()) {
240 final Bundle args = new Bundle();
241 args.putParcelable(EXTRA_TEMPLATE, parent.getNetworkTemplate());
242 args.putBoolean(EXTRA_LIMIT, isLimit);
244 final BytesEditorFragment dialog = new BytesEditorFragment();
245 dialog.setArguments(args);
246 dialog.setTargetFragment(targetFragment, 0);
247 dialog.show(targetFragment.getFragmentManager(), TAG_WARNING_EDITOR);
251 public Dialog onCreateDialog(Bundle savedInstanceState) {
252 final Context context = getActivity();
253 final LayoutInflater dialogInflater = LayoutInflater.from(context);
254 final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT);
255 mView = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
256 setupPicker((EditText) mView.findViewById(R.id.bytes),
257 (Spinner) mView.findViewById(R.id.size_spinner));
258 return new AlertDialog.Builder(context)
259 .setTitle(isLimit ? R.string.data_usage_limit_editor_title
260 : R.string.data_usage_warning_editor_title)
262 .setPositiveButton(R.string.data_usage_cycle_editor_positive, this)
266 private void setupPicker(EditText bytesPicker, Spinner type) {
267 final DataUsageEditController target = (DataUsageEditController) getTargetFragment();
268 final NetworkPolicyEditor editor = target.getNetworkPolicyEditor();
270 final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
271 final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT);
272 final long bytes = isLimit ? editor.getPolicyLimitBytes(template)
273 : editor.getPolicyWarningBytes(template);
274 final long limitDisabled = isLimit ? LIMIT_DISABLED : WARNING_DISABLED;
276 if (bytes > 1.5f * GIB_IN_BYTES) {
277 final String bytesText = formatText(bytes / (float) GIB_IN_BYTES);
278 bytesPicker.setText(bytesText);
279 bytesPicker.setSelection(0, bytesText.length());
281 type.setSelection(1);
283 final String bytesText = formatText(bytes / (float) MIB_IN_BYTES);
284 bytesPicker.setText(bytesText);
285 bytesPicker.setSelection(0, bytesText.length());
287 type.setSelection(0);
291 private String formatText(float v) {
292 v = Math.round(v * 100) / 100f;
293 return String.valueOf(v);
297 public void onClick(DialogInterface dialog, int which) {
298 if (which != DialogInterface.BUTTON_POSITIVE) {
301 final DataUsageEditController target = (DataUsageEditController) getTargetFragment();
302 final NetworkPolicyEditor editor = target.getNetworkPolicyEditor();
304 final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
305 final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT);
306 EditText bytesField = (EditText) mView.findViewById(R.id.bytes);
307 Spinner spinner = (Spinner) mView.findViewById(R.id.size_spinner);
309 String bytesString = bytesField.getText().toString();
310 if (bytesString.isEmpty() || bytesString.equals(".")) {
313 final long bytes = (long) (Float.valueOf(bytesString)
314 * (spinner.getSelectedItemPosition() == 0 ? MIB_IN_BYTES : GIB_IN_BYTES));
316 // to fix the overflow problem
317 final long correctedBytes = Math.min(MAX_DATA_LIMIT_BYTES, bytes);
319 editor.setPolicyLimitBytes(template, correctedBytes);
321 editor.setPolicyWarningBytes(template, correctedBytes);
323 target.updateDataUsage();
327 public int getMetricsCategory() {
328 return MetricsEvent.DIALOG_BILLING_BYTE_LIMIT;
333 * Dialog to edit {@link NetworkPolicy}.
335 public static class CycleEditorFragment extends InstrumentedDialogFragment implements
336 DialogInterface.OnClickListener {
337 private static final String EXTRA_TEMPLATE = "template";
338 private NumberPicker mCycleDayPicker;
340 public static void show(BillingCycleSettings parent) {
341 if (!parent.isAdded()) return;
343 final Bundle args = new Bundle();
344 args.putParcelable(EXTRA_TEMPLATE, parent.mNetworkTemplate);
346 final CycleEditorFragment dialog = new CycleEditorFragment();
347 dialog.setArguments(args);
348 dialog.setTargetFragment(parent, 0);
349 dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
353 public int getMetricsCategory() {
354 return MetricsEvent.DIALOG_BILLING_CYCLE;
358 public Dialog onCreateDialog(Bundle savedInstanceState) {
359 final Context context = getActivity();
360 final DataUsageEditController target = (DataUsageEditController) getTargetFragment();
361 final NetworkPolicyEditor editor = target.getNetworkPolicyEditor();
363 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
364 final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
366 final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
367 mCycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
369 final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
370 final int cycleDay = editor.getPolicyCycleDay(template);
372 mCycleDayPicker.setMinValue(1);
373 mCycleDayPicker.setMaxValue(31);
374 mCycleDayPicker.setValue(cycleDay);
375 mCycleDayPicker.setWrapSelectorWheel(true);
377 return builder.setTitle(R.string.data_usage_cycle_editor_title)
379 .setPositiveButton(R.string.data_usage_cycle_editor_positive, this)
384 public void onClick(DialogInterface dialog, int which) {
385 final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
386 final DataUsageEditController target = (DataUsageEditController) getTargetFragment();
387 final NetworkPolicyEditor editor = target.getNetworkPolicyEditor();
389 // clear focus to finish pending text edits
390 mCycleDayPicker.clearFocus();
392 final int cycleDay = mCycleDayPicker.getValue();
393 final String cycleTimezone = new Time().timezone;
394 editor.setPolicyCycleDay(template, cycleDay, cycleTimezone);
395 target.updateDataUsage();
400 * Dialog to request user confirmation before setting
401 * {@link NetworkPolicy#limitBytes}.
403 public static class ConfirmLimitFragment extends InstrumentedDialogFragment implements
404 DialogInterface.OnClickListener {
405 private static final String EXTRA_MESSAGE = "message";
406 @VisibleForTesting static final String EXTRA_LIMIT_BYTES = "limitBytes";
407 public static final float FLOAT = 1.2f;
409 public static void show(BillingCycleSettings parent) {
410 if (!parent.isAdded()) return;
412 final NetworkPolicy policy = parent.services.mPolicyEditor
413 .getPolicy(parent.mNetworkTemplate);
414 if (policy == null) return;
416 final Resources res = parent.getResources();
417 final CharSequence message;
418 final long minLimitBytes = (long) (policy.warningBytes * FLOAT);
419 final long limitBytes;
421 // TODO: customize default limits based on network template
422 message = res.getString(R.string.data_usage_limit_dialog_mobile);
423 limitBytes = Math.max(5 * GIB_IN_BYTES, minLimitBytes);
425 final Bundle args = new Bundle();
426 args.putCharSequence(EXTRA_MESSAGE, message);
427 args.putLong(EXTRA_LIMIT_BYTES, limitBytes);
429 final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
430 dialog.setArguments(args);
431 dialog.setTargetFragment(parent, 0);
432 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
436 public int getMetricsCategory() {
437 return MetricsEvent.DIALOG_BILLING_CONFIRM_LIMIT;
441 public Dialog onCreateDialog(Bundle savedInstanceState) {
442 final Context context = getActivity();
444 final CharSequence message = getArguments().getCharSequence(EXTRA_MESSAGE);
446 return new AlertDialog.Builder(context)
447 .setTitle(R.string.data_usage_limit_dialog_title)
449 .setPositiveButton(android.R.string.ok, this)
450 .setNegativeButton(android.R.string.cancel, null)
455 public void onClick(DialogInterface dialog, int which) {
456 final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment();
457 if (which != DialogInterface.BUTTON_POSITIVE) return;
458 final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES);
459 if (target != null) {
460 target.setPolicyLimitBytes(limitBytes);
462 target.getPreferenceManager().getSharedPreferences().edit()
463 .putBoolean(KEY_SET_DATA_LIMIT, true).apply();