• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
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
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
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.
13  */
14 
15 package com.android.settings.datausage;
16 
17 import static android.net.NetworkPolicy.CYCLE_NONE;
18 import static android.net.NetworkPolicy.LIMIT_DISABLED;
19 import static android.net.NetworkPolicy.WARNING_DISABLED;
20 
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.icu.text.MeasureFormat;
28 import android.icu.util.MeasureUnit;
29 import android.net.NetworkPolicy;
30 import android.net.NetworkTemplate;
31 import android.os.Bundle;
32 import android.support.v14.preference.SwitchPreference;
33 import android.support.v7.preference.Preference;
34 import android.text.format.Formatter;
35 import android.text.format.Time;
36 import android.util.Log;
37 import android.view.LayoutInflater;
38 import android.view.View;
39 import android.widget.ArrayAdapter;
40 import android.widget.EditText;
41 import android.widget.NumberPicker;
42 import android.widget.Spinner;
43 
44 import com.android.internal.annotations.VisibleForTesting;
45 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
46 import com.android.settings.R;
47 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
48 import com.android.settingslib.NetworkPolicyEditor;
49 import com.android.settingslib.net.DataUsageController;
50 
51 public class BillingCycleSettings extends DataUsageBase implements
52         Preference.OnPreferenceChangeListener, DataUsageEditController {
53 
54     private static final String TAG = "BillingCycleSettings";
55     private static final boolean LOGD = false;
56     public static final long KB_IN_BYTES = 1000;
57     public static final long MB_IN_BYTES = KB_IN_BYTES * 1000;
58     public static final long GB_IN_BYTES = MB_IN_BYTES * 1000;
59 
60     private static final long MAX_DATA_LIMIT_BYTES = 50000 * GB_IN_BYTES;
61 
62     private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
63     private static final String TAG_CYCLE_EDITOR = "cycleEditor";
64     private static final String TAG_WARNING_EDITOR = "warningEditor";
65 
66     private static final String KEY_BILLING_CYCLE = "billing_cycle";
67     private static final String KEY_SET_DATA_WARNING = "set_data_warning";
68     private static final String KEY_DATA_WARNING = "data_warning";
69     @VisibleForTesting static final String KEY_SET_DATA_LIMIT = "set_data_limit";
70     private static final String KEY_DATA_LIMIT = "data_limit";
71 
72     private NetworkTemplate mNetworkTemplate;
73     private Preference mBillingCycle;
74     private Preference mDataWarning;
75     private SwitchPreference mEnableDataWarning;
76     private SwitchPreference mEnableDataLimit;
77     private Preference mDataLimit;
78     private DataUsageController mDataUsageController;
79 
80     @Override
onCreate(Bundle icicle)81     public void onCreate(Bundle icicle) {
82         super.onCreate(icicle);
83 
84         mDataUsageController = new DataUsageController(getContext());
85 
86         Bundle args = getArguments();
87         mNetworkTemplate = args.getParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE);
88 
89         addPreferencesFromResource(R.xml.billing_cycle);
90         mBillingCycle = findPreference(KEY_BILLING_CYCLE);
91         mEnableDataWarning = (SwitchPreference) findPreference(KEY_SET_DATA_WARNING);
92         mEnableDataWarning.setOnPreferenceChangeListener(this);
93         mDataWarning = findPreference(KEY_DATA_WARNING);
94         mEnableDataLimit = (SwitchPreference) findPreference(KEY_SET_DATA_LIMIT);
95         mEnableDataLimit.setOnPreferenceChangeListener(this);
96         mDataLimit = findPreference(KEY_DATA_LIMIT);
97     }
98 
99     @Override
onResume()100     public void onResume() {
101         super.onResume();
102         updatePrefs();
103     }
104 
updatePrefs()105     private void updatePrefs() {
106         final int cycleDay = services.mPolicyEditor.getPolicyCycleDay(mNetworkTemplate);
107         if (cycleDay != CYCLE_NONE) {
108             mBillingCycle.setSummary(getString(R.string.billing_cycle_fragment_summary, cycleDay));
109         } else {
110             mBillingCycle.setSummary(null);
111         }
112         final long warningBytes = services.mPolicyEditor.getPolicyWarningBytes(mNetworkTemplate);
113         if (warningBytes != WARNING_DISABLED) {
114             mDataWarning.setSummary(Formatter.formatFileSize(getContext(), warningBytes));
115             mDataWarning.setEnabled(true);
116             mEnableDataWarning.setChecked(true);
117         } else {
118             mDataWarning.setSummary(null);
119             mDataWarning.setEnabled(false);
120             mEnableDataWarning.setChecked(false);
121         }
122         final long limitBytes = services.mPolicyEditor.getPolicyLimitBytes(mNetworkTemplate);
123         if (limitBytes != LIMIT_DISABLED) {
124             mDataLimit.setSummary(Formatter.formatFileSize(getContext(), limitBytes));
125             mDataLimit.setEnabled(true);
126             mEnableDataLimit.setChecked(true);
127         } else {
128             mDataLimit.setSummary(null);
129             mDataLimit.setEnabled(false);
130             mEnableDataLimit.setChecked(false);
131         }
132     }
133 
134     @Override
onPreferenceTreeClick(Preference preference)135     public boolean onPreferenceTreeClick(Preference preference) {
136         if (preference == mBillingCycle) {
137             CycleEditorFragment.show(this);
138             return true;
139         } else if (preference == mDataWarning) {
140             BytesEditorFragment.show(this, false);
141             return true;
142         } else if (preference == mDataLimit) {
143             BytesEditorFragment.show(this, true);
144             return true;
145         }
146         return super.onPreferenceTreeClick(preference);
147     }
148 
149     @Override
onPreferenceChange(Preference preference, Object newValue)150     public boolean onPreferenceChange(Preference preference, Object newValue) {
151         if (mEnableDataLimit == preference) {
152             boolean enabled = (Boolean) newValue;
153             if (!enabled) {
154                 setPolicyLimitBytes(LIMIT_DISABLED);
155                 return true;
156             }
157             ConfirmLimitFragment.show(this);
158             // This preference is enabled / disabled by ConfirmLimitFragment.
159             return false;
160         } else if (mEnableDataWarning == preference) {
161             boolean enabled = (Boolean) newValue;
162             if (enabled) {
163                 setPolicyWarningBytes(mDataUsageController.getDefaultWarningLevel());
164             } else {
165                 setPolicyWarningBytes(WARNING_DISABLED);
166             }
167             return true;
168         }
169         return false;
170     }
171 
172     @Override
getMetricsCategory()173     public int getMetricsCategory() {
174         return MetricsEvent.BILLING_CYCLE;
175     }
176 
177     @VisibleForTesting
setPolicyLimitBytes(long limitBytes)178     void setPolicyLimitBytes(long limitBytes) {
179         if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
180         services.mPolicyEditor.setPolicyLimitBytes(mNetworkTemplate, limitBytes);
181         updatePrefs();
182     }
183 
setPolicyWarningBytes(long warningBytes)184     private void setPolicyWarningBytes(long warningBytes) {
185         if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
186         services.mPolicyEditor.setPolicyWarningBytes(mNetworkTemplate, warningBytes);
187         updatePrefs();
188     }
189 
190     @Override
getNetworkPolicyEditor()191     public NetworkPolicyEditor getNetworkPolicyEditor() {
192         return services.mPolicyEditor;
193     }
194 
195     @Override
getNetworkTemplate()196     public NetworkTemplate getNetworkTemplate() {
197         return mNetworkTemplate;
198     }
199 
200     @Override
updateDataUsage()201     public void updateDataUsage() {
202         updatePrefs();
203     }
204 
205     /**
206      * Dialog to edit {@link NetworkPolicy#warningBytes}.
207      */
208     public static class BytesEditorFragment extends InstrumentedDialogFragment
209             implements DialogInterface.OnClickListener {
210         private static final String EXTRA_TEMPLATE = "template";
211         private static final String EXTRA_LIMIT = "limit";
212         private View mView;
213 
show(DataUsageEditController parent, boolean isLimit)214         public static void show(DataUsageEditController parent, boolean isLimit) {
215             if (!(parent instanceof Fragment)) {
216                 return;
217             }
218             Fragment targetFragment = (Fragment) parent;
219             if (!targetFragment.isAdded()) {
220                 return;
221             }
222 
223             final Bundle args = new Bundle();
224             args.putParcelable(EXTRA_TEMPLATE, parent.getNetworkTemplate());
225             args.putBoolean(EXTRA_LIMIT, isLimit);
226 
227             final BytesEditorFragment dialog = new BytesEditorFragment();
228             dialog.setArguments(args);
229             dialog.setTargetFragment(targetFragment, 0);
230             dialog.show(targetFragment.getFragmentManager(), TAG_WARNING_EDITOR);
231         }
232 
233         @Override
onCreateDialog(Bundle savedInstanceState)234         public Dialog onCreateDialog(Bundle savedInstanceState) {
235             final Context context = getActivity();
236             final LayoutInflater dialogInflater = LayoutInflater.from(context);
237             final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT);
238             mView = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
239             setupPicker((EditText) mView.findViewById(R.id.bytes),
240                     (Spinner) mView.findViewById(R.id.size_spinner));
241             return new AlertDialog.Builder(context)
242                     .setTitle(isLimit ? R.string.data_usage_limit_editor_title
243                             : R.string.data_usage_warning_editor_title)
244                     .setView(mView)
245                     .setPositiveButton(R.string.data_usage_cycle_editor_positive, this)
246                     .create();
247         }
248 
setupPicker(EditText bytesPicker, Spinner type)249         private void setupPicker(EditText bytesPicker, Spinner type) {
250             final DataUsageEditController target = (DataUsageEditController) getTargetFragment();
251             final NetworkPolicyEditor editor = target.getNetworkPolicyEditor();
252 
253             final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
254             final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT);
255             final long bytes = isLimit ? editor.getPolicyLimitBytes(template)
256                     : editor.getPolicyWarningBytes(template);
257             final long limitDisabled = isLimit ? LIMIT_DISABLED : WARNING_DISABLED;
258 
259             final MeasureFormat formatter = MeasureFormat.getInstance(
260                     getContext().getResources().getConfiguration().locale,
261                     MeasureFormat.FormatWidth.SHORT);
262             final String[] unitNames = new String[] {
263                 formatter.getUnitDisplayName(MeasureUnit.MEGABYTE),
264                 formatter.getUnitDisplayName(MeasureUnit.GIGABYTE)
265             };
266             final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
267                     getContext(), R.layout.data_usage_spinner_item, unitNames);
268             type.setAdapter(adapter);
269 
270             if (bytes > 1.5f * GB_IN_BYTES) {
271                 final String bytesText = formatText(bytes / (float) GB_IN_BYTES);
272                 bytesPicker.setText(bytesText);
273                 bytesPicker.setSelection(0, bytesText.length());
274 
275                 type.setSelection(1);
276             } else {
277                 final String bytesText = formatText(bytes / (float) MB_IN_BYTES);
278                 bytesPicker.setText(bytesText);
279                 bytesPicker.setSelection(0, bytesText.length());
280 
281                 type.setSelection(0);
282             }
283         }
284 
formatText(float v)285         private String formatText(float v) {
286             v = Math.round(v * 100) / 100f;
287             return String.valueOf(v);
288         }
289 
290         @Override
onClick(DialogInterface dialog, int which)291         public void onClick(DialogInterface dialog, int which) {
292             if (which != DialogInterface.BUTTON_POSITIVE) {
293                 return;
294             }
295             final DataUsageEditController target = (DataUsageEditController) getTargetFragment();
296             final NetworkPolicyEditor editor = target.getNetworkPolicyEditor();
297 
298             final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
299             final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT);
300             EditText bytesField = (EditText) mView.findViewById(R.id.bytes);
301             Spinner spinner = (Spinner) mView.findViewById(R.id.size_spinner);
302 
303             String bytesString = bytesField.getText().toString();
304             if (bytesString.isEmpty() || bytesString.equals(".")) {
305                 bytesString = "0";
306             }
307             final long bytes = (long) (Float.valueOf(bytesString)
308                         * (spinner.getSelectedItemPosition() == 0 ? MB_IN_BYTES : GB_IN_BYTES));
309 
310             // to fix the overflow problem
311             final long correctedBytes = Math.min(MAX_DATA_LIMIT_BYTES, bytes);
312             if (isLimit) {
313                 editor.setPolicyLimitBytes(template, correctedBytes);
314             } else {
315                 editor.setPolicyWarningBytes(template, correctedBytes);
316             }
317             target.updateDataUsage();
318         }
319 
320         @Override
getMetricsCategory()321         public int getMetricsCategory() {
322             return MetricsEvent.DIALOG_BILLING_BYTE_LIMIT;
323         }
324     }
325 
326     /**
327      * Dialog to edit {@link NetworkPolicy}.
328      */
329     public static class CycleEditorFragment extends InstrumentedDialogFragment implements
330             DialogInterface.OnClickListener {
331         private static final String EXTRA_TEMPLATE = "template";
332         private NumberPicker mCycleDayPicker;
333 
show(BillingCycleSettings parent)334         public static void show(BillingCycleSettings parent) {
335             if (!parent.isAdded()) return;
336 
337             final Bundle args = new Bundle();
338             args.putParcelable(EXTRA_TEMPLATE, parent.mNetworkTemplate);
339 
340             final CycleEditorFragment dialog = new CycleEditorFragment();
341             dialog.setArguments(args);
342             dialog.setTargetFragment(parent, 0);
343             dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
344         }
345 
346         @Override
getMetricsCategory()347         public int getMetricsCategory() {
348             return MetricsEvent.DIALOG_BILLING_CYCLE;
349         }
350 
351         @Override
onCreateDialog(Bundle savedInstanceState)352         public Dialog onCreateDialog(Bundle savedInstanceState) {
353             final Context context = getActivity();
354             final DataUsageEditController target = (DataUsageEditController) getTargetFragment();
355             final NetworkPolicyEditor editor = target.getNetworkPolicyEditor();
356 
357             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
358             final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
359 
360             final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
361             mCycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
362 
363             final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
364             final int cycleDay = editor.getPolicyCycleDay(template);
365 
366             mCycleDayPicker.setMinValue(1);
367             mCycleDayPicker.setMaxValue(31);
368             mCycleDayPicker.setValue(cycleDay);
369             mCycleDayPicker.setWrapSelectorWheel(true);
370 
371             return builder.setTitle(R.string.data_usage_cycle_editor_title)
372                     .setView(view)
373                     .setPositiveButton(R.string.data_usage_cycle_editor_positive, this)
374                     .create();
375         }
376 
377         @Override
onClick(DialogInterface dialog, int which)378         public void onClick(DialogInterface dialog, int which) {
379             final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
380             final DataUsageEditController target = (DataUsageEditController) getTargetFragment();
381             final NetworkPolicyEditor editor = target.getNetworkPolicyEditor();
382 
383             // clear focus to finish pending text edits
384             mCycleDayPicker.clearFocus();
385 
386             final int cycleDay = mCycleDayPicker.getValue();
387             final String cycleTimezone = new Time().timezone;
388             editor.setPolicyCycleDay(template, cycleDay, cycleTimezone);
389             target.updateDataUsage();
390         }
391     }
392 
393     /**
394      * Dialog to request user confirmation before setting
395      * {@link NetworkPolicy#limitBytes}.
396      */
397     public static class ConfirmLimitFragment extends InstrumentedDialogFragment implements
398             DialogInterface.OnClickListener {
399         private static final String EXTRA_MESSAGE = "message";
400         @VisibleForTesting static final String EXTRA_LIMIT_BYTES = "limitBytes";
401         public static final float FLOAT = 1.2f;
402 
show(BillingCycleSettings parent)403         public static void show(BillingCycleSettings parent) {
404             if (!parent.isAdded()) return;
405 
406             final NetworkPolicy policy = parent.services.mPolicyEditor
407                     .getPolicy(parent.mNetworkTemplate);
408             if (policy == null) return;
409 
410             final Resources res = parent.getResources();
411             final CharSequence message;
412             final long minLimitBytes = (long) (policy.warningBytes * FLOAT);
413             final long limitBytes;
414 
415             // TODO: customize default limits based on network template
416             message = res.getString(R.string.data_usage_limit_dialog_mobile);
417             limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes);
418 
419             final Bundle args = new Bundle();
420             args.putCharSequence(EXTRA_MESSAGE, message);
421             args.putLong(EXTRA_LIMIT_BYTES, limitBytes);
422 
423             final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
424             dialog.setArguments(args);
425             dialog.setTargetFragment(parent, 0);
426             dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
427         }
428 
429         @Override
getMetricsCategory()430         public int getMetricsCategory() {
431             return MetricsEvent.DIALOG_BILLING_CONFIRM_LIMIT;
432         }
433 
434         @Override
onCreateDialog(Bundle savedInstanceState)435         public Dialog onCreateDialog(Bundle savedInstanceState) {
436             final Context context = getActivity();
437 
438             final CharSequence message = getArguments().getCharSequence(EXTRA_MESSAGE);
439 
440             return new AlertDialog.Builder(context)
441                     .setTitle(R.string.data_usage_limit_dialog_title)
442                     .setMessage(message)
443                     .setPositiveButton(android.R.string.ok, this)
444                     .setNegativeButton(android.R.string.cancel, null)
445                     .create();
446         }
447 
448         @Override
onClick(DialogInterface dialog, int which)449         public void onClick(DialogInterface dialog, int which) {
450             final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment();
451             if (which != DialogInterface.BUTTON_POSITIVE) return;
452             final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES);
453             if (target != null) {
454                 target.setPolicyLimitBytes(limitBytes);
455             }
456             target.getPreferenceManager().getSharedPreferences().edit()
457                     .putBoolean(KEY_SET_DATA_LIMIT, true).apply();
458         }
459     }
460 }
461