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 android.app.AlertDialog;
18 import android.app.Dialog;
19 import android.app.DialogFragment;
20 import android.content.Context;
21 import android.content.DialogInterface;
22 import android.content.res.Resources;
23 import android.net.NetworkPolicy;
24 import android.net.NetworkTemplate;
25 import android.os.Bundle;
26 import android.support.v14.preference.SwitchPreference;
27 import android.support.v7.preference.Preference;
28 import android.text.format.Formatter;
29 import android.text.format.Time;
30 import android.util.Log;
31 import android.view.LayoutInflater;
32 import android.view.View;
33 import android.widget.EditText;
34 import android.widget.NumberPicker;
35 import android.widget.Spinner;
36 import com.android.internal.logging.MetricsProto.MetricsEvent;
37 import com.android.settings.R;
38 import com.android.settingslib.NetworkPolicyEditor;
39 import com.android.settingslib.net.DataUsageController;
40 
41 import static android.net.NetworkPolicy.LIMIT_DISABLED;
42 import static android.net.NetworkPolicy.WARNING_DISABLED;
43 import static android.net.TrafficStats.GB_IN_BYTES;
44 import static android.net.TrafficStats.MB_IN_BYTES;
45 
46 public class BillingCycleSettings extends DataUsageBase implements
47         Preference.OnPreferenceChangeListener {
48 
49     private static final String TAG = "BillingCycleSettings";
50     private static final boolean LOGD = false;
51 
52     private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
53     private static final String TAG_CYCLE_EDITOR = "cycleEditor";
54     private static final String TAG_WARNING_EDITOR = "warningEditor";
55 
56     private static final String KEY_BILLING_CYCLE = "billing_cycle";
57     private static final String KEY_DATA_WARNING = "data_warning";
58     private static final String KEY_SET_DATA_LIMIT = "set_data_limit";
59     private static final String KEY_DATA_LIMIT = "data_limit";
60 
61     private NetworkTemplate mNetworkTemplate;
62     private Preference mBillingCycle;
63     private Preference mDataWarning;
64     private SwitchPreference mEnableDataLimit;
65     private Preference mDataLimit;
66     private DataUsageController mDataUsageController;
67 
68     @Override
onCreate(Bundle icicle)69     public void onCreate(Bundle icicle) {
70         super.onCreate(icicle);
71 
72         mDataUsageController = new DataUsageController(getContext());
73 
74         Bundle args = getArguments();
75         mNetworkTemplate = args.getParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE);
76 
77         addPreferencesFromResource(R.xml.billing_cycle);
78         mBillingCycle = findPreference(KEY_BILLING_CYCLE);
79         mDataWarning = findPreference(KEY_DATA_WARNING);
80         mEnableDataLimit = (SwitchPreference) findPreference(KEY_SET_DATA_LIMIT);
81         mEnableDataLimit.setOnPreferenceChangeListener(this);
82         mDataLimit = findPreference(KEY_DATA_LIMIT);
83     }
84 
85     @Override
onResume()86     public void onResume() {
87         super.onResume();
88         updatePrefs();
89     }
90 
updatePrefs()91     private void updatePrefs() {
92         NetworkPolicy policy = services.mPolicyEditor.getPolicy(mNetworkTemplate);
93         mBillingCycle.setSummary(getString(R.string.billing_cycle_summary, policy != null ?
94                 policy.cycleDay : 1));
95         mDataWarning.setSummary(Formatter.formatFileSize(getContext(), policy != null
96                 ? policy.warningBytes : DataUsageController.DEFAULT_WARNING_LEVEL));
97         if (policy != null && policy.limitBytes != LIMIT_DISABLED) {
98             mDataLimit.setSummary(Formatter.formatFileSize(getContext(), policy.limitBytes));
99             mDataLimit.setEnabled(true);
100             mEnableDataLimit.setChecked(true);
101         } else {
102             mDataLimit.setSummary(null);
103             mDataLimit.setEnabled(false);
104             mEnableDataLimit.setChecked(false);
105         }
106     }
107 
108     @Override
onPreferenceTreeClick(Preference preference)109     public boolean onPreferenceTreeClick(Preference preference) {
110         if (preference == mBillingCycle) {
111             CycleEditorFragment.show(this);
112             return true;
113         } else if (preference == mDataWarning) {
114             BytesEditorFragment.show(this, false);
115             return true;
116         } else if (preference == mDataLimit) {
117             BytesEditorFragment.show(this, true);
118             return true;
119         }
120         return super.onPreferenceTreeClick(preference);
121     }
122 
123     @Override
onPreferenceChange(Preference preference, Object newValue)124     public boolean onPreferenceChange(Preference preference, Object newValue) {
125         if (mEnableDataLimit == preference) {
126             boolean enabled = (Boolean) newValue;
127             if (enabled) {
128                 ConfirmLimitFragment.show(this);
129             } else {
130                 setPolicyLimitBytes(LIMIT_DISABLED);
131             }
132             return true;
133         }
134         return false;
135     }
136 
137     @Override
getMetricsCategory()138     protected int getMetricsCategory() {
139         return MetricsEvent.BILLING_CYCLE;
140     }
141 
setPolicyLimitBytes(long limitBytes)142     private void setPolicyLimitBytes(long limitBytes) {
143         if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
144         services.mPolicyEditor.setPolicyLimitBytes(mNetworkTemplate, limitBytes);
145         updatePrefs();
146     }
147 
148     /**
149      * Dialog to edit {@link NetworkPolicy#warningBytes}.
150      */
151     public static class BytesEditorFragment extends DialogFragment
152             implements DialogInterface.OnClickListener{
153         private static final String EXTRA_TEMPLATE = "template";
154         private static final String EXTRA_LIMIT = "limit";
155         private View mView;
156 
show(BillingCycleSettings parent, boolean isLimit)157         public static void show(BillingCycleSettings parent, boolean isLimit) {
158             if (!parent.isAdded()) return;
159 
160             final Bundle args = new Bundle();
161             args.putParcelable(EXTRA_TEMPLATE, parent.mNetworkTemplate);
162             args.putBoolean(EXTRA_LIMIT, isLimit);
163 
164             final BytesEditorFragment dialog = new BytesEditorFragment();
165             dialog.setArguments(args);
166             dialog.setTargetFragment(parent, 0);
167             dialog.show(parent.getFragmentManager(), TAG_WARNING_EDITOR);
168         }
169 
170         @Override
onCreateDialog(Bundle savedInstanceState)171         public Dialog onCreateDialog(Bundle savedInstanceState) {
172             final Context context = getActivity();
173 
174 
175             final LayoutInflater dialogInflater = LayoutInflater.from(context);
176             final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT);
177             mView = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
178             setupPicker((EditText) mView.findViewById(R.id.bytes),
179                     (Spinner) mView.findViewById(R.id.size_spinner));
180             return new AlertDialog.Builder(context)
181                     .setTitle(isLimit ? R.string.data_usage_limit_editor_title
182                             : R.string.data_usage_warning_editor_title)
183                     .setView(mView)
184                     .setPositiveButton(R.string.data_usage_cycle_editor_positive, this)
185                     .create();
186         }
187 
setupPicker(EditText bytesPicker, Spinner type)188         private void setupPicker(EditText bytesPicker, Spinner type) {
189             final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment();
190             final NetworkPolicyEditor editor = target.services.mPolicyEditor;
191 
192             final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
193             final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT);
194             final long bytes = isLimit ? editor.getPolicyLimitBytes(template)
195                     : editor.getPolicyWarningBytes(template);
196             final long limitDisabled = isLimit ? LIMIT_DISABLED : WARNING_DISABLED;
197 
198             if (bytes > 1.5f * GB_IN_BYTES) {
199                 bytesPicker.setText(formatText(bytes / (float) GB_IN_BYTES));
200                 type.setSelection(1);
201             } else {
202                 bytesPicker.setText(formatText(bytes / (float) MB_IN_BYTES));
203                 type.setSelection(0);
204             }
205         }
206 
formatText(float v)207         private String formatText(float v) {
208             v = Math.round(v * 100) / 100f;
209             return String.valueOf(v);
210         }
211 
212         @Override
onClick(DialogInterface dialog, int which)213         public void onClick(DialogInterface dialog, int which) {
214             if (which != DialogInterface.BUTTON_POSITIVE) {
215                 return;
216             }
217             final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment();
218             final NetworkPolicyEditor editor = target.services.mPolicyEditor;
219 
220             final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
221             final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT);
222             EditText bytesField = (EditText) mView.findViewById(R.id.bytes);
223             Spinner spinner = (Spinner) mView.findViewById(R.id.size_spinner);
224 
225             String bytesString = bytesField.getText().toString();
226             if (bytesString.isEmpty()) {
227                 bytesString = "0";
228             }
229             final long bytes = (long) (Float.valueOf(bytesString)
230                         * (spinner.getSelectedItemPosition() == 0 ? MB_IN_BYTES : GB_IN_BYTES));
231             if (isLimit) {
232                 editor.setPolicyLimitBytes(template, bytes);
233             } else {
234                 editor.setPolicyWarningBytes(template, bytes);
235             }
236             target.updatePrefs();
237         }
238     }
239 
240     /**
241      * Dialog to edit {@link NetworkPolicy#cycleDay}.
242      */
243     public static class CycleEditorFragment extends DialogFragment implements
244             DialogInterface.OnClickListener{
245         private static final String EXTRA_TEMPLATE = "template";
246         private NumberPicker mCycleDayPicker;
247 
show(BillingCycleSettings parent)248         public static void show(BillingCycleSettings parent) {
249             if (!parent.isAdded()) return;
250 
251             final Bundle args = new Bundle();
252             args.putParcelable(EXTRA_TEMPLATE, parent.mNetworkTemplate);
253 
254             final CycleEditorFragment dialog = new CycleEditorFragment();
255             dialog.setArguments(args);
256             dialog.setTargetFragment(parent, 0);
257             dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
258         }
259 
260         @Override
onCreateDialog(Bundle savedInstanceState)261         public Dialog onCreateDialog(Bundle savedInstanceState) {
262             final Context context = getActivity();
263             final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment();
264             final NetworkPolicyEditor editor = target.services.mPolicyEditor;
265 
266             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
267             final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
268 
269             final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
270             mCycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
271 
272             final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
273             final int cycleDay = editor.getPolicyCycleDay(template);
274 
275             mCycleDayPicker.setMinValue(1);
276             mCycleDayPicker.setMaxValue(31);
277             mCycleDayPicker.setValue(cycleDay);
278             mCycleDayPicker.setWrapSelectorWheel(true);
279 
280             return builder.setTitle(R.string.data_usage_cycle_editor_title)
281                     .setView(view)
282                     .setPositiveButton(R.string.data_usage_cycle_editor_positive, this)
283                     .create();
284         }
285 
286         @Override
onClick(DialogInterface dialog, int which)287         public void onClick(DialogInterface dialog, int which) {
288             final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
289             final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment();
290             final NetworkPolicyEditor editor = target.services.mPolicyEditor;
291 
292             // clear focus to finish pending text edits
293             mCycleDayPicker.clearFocus();
294 
295             final int cycleDay = mCycleDayPicker.getValue();
296             final String cycleTimezone = new Time().timezone;
297             editor.setPolicyCycleDay(template, cycleDay, cycleTimezone);
298             target.updatePrefs();
299         }
300     }
301 
302     /**
303      * Dialog to request user confirmation before setting
304      * {@link NetworkPolicy#limitBytes}.
305      */
306     public static class ConfirmLimitFragment extends DialogFragment implements
307             DialogInterface.OnClickListener{
308         private static final String EXTRA_MESSAGE = "message";
309         private static final String EXTRA_LIMIT_BYTES = "limitBytes";
310         public static final float FLOAT = 1.2f;
311 
show(BillingCycleSettings parent)312         public static void show(BillingCycleSettings parent) {
313             if (!parent.isAdded()) return;
314 
315             final NetworkPolicy policy = parent.services.mPolicyEditor
316                     .getPolicy(parent.mNetworkTemplate);
317             if (policy == null) return;
318 
319             final Resources res = parent.getResources();
320             final CharSequence message;
321             final long minLimitBytes = (long) (policy.warningBytes * FLOAT);
322             final long limitBytes;
323 
324             // TODO: customize default limits based on network template
325             message = res.getString(R.string.data_usage_limit_dialog_mobile);
326             limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes);
327 
328             final Bundle args = new Bundle();
329             args.putCharSequence(EXTRA_MESSAGE, message);
330             args.putLong(EXTRA_LIMIT_BYTES, limitBytes);
331 
332             final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
333             dialog.setArguments(args);
334             dialog.setTargetFragment(parent, 0);
335             dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
336         }
337 
338         @Override
onCreateDialog(Bundle savedInstanceState)339         public Dialog onCreateDialog(Bundle savedInstanceState) {
340             final Context context = getActivity();
341 
342             final CharSequence message = getArguments().getCharSequence(EXTRA_MESSAGE);
343 
344             return new AlertDialog.Builder(context)
345                     .setTitle(R.string.data_usage_limit_dialog_title)
346                     .setMessage(message)
347                     .setPositiveButton(android.R.string.ok, this)
348                     .setNegativeButton(android.R.string.cancel, null)
349                     .create();
350         }
351 
352         @Override
onClick(DialogInterface dialog, int which)353         public void onClick(DialogInterface dialog, int which) {
354             if (which != DialogInterface.BUTTON_POSITIVE) return;
355             final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES);
356             final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment();
357             if (target != null) {
358                 target.setPolicyLimitBytes(limitBytes);
359             }
360         }
361     }
362 }
363