• 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.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;
40 
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;
48 
49 public class BillingCycleSettings extends DataUsageBase implements
50         Preference.OnPreferenceChangeListener, DataUsageEditController {
51 
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;
56 
57     private static final long MAX_DATA_LIMIT_BYTES = 50000 * GIB_IN_BYTES;
58 
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";
62 
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";
68 
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;
76 
77     @VisibleForTesting
setUpForTest(NetworkPolicyEditor policyEditor, Preference billingCycle, Preference dataLimit, Preference dataWarning, SwitchPreference enableLimit, SwitchPreference enableWarning)78     void setUpForTest(NetworkPolicyEditor policyEditor,
79                       Preference billingCycle,
80                       Preference dataLimit,
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;
90     }
91 
92     @Override
onCreate(Bundle icicle)93     public void onCreate(Bundle icicle) {
94         super.onCreate(icicle);
95 
96         mDataUsageController = new DataUsageController(getContext());
97 
98         Bundle args = getArguments();
99         mNetworkTemplate = args.getParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE);
100 
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);
109 
110         mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.data_warning_footnote);
111     }
112 
113     @Override
onResume()114     public void onResume() {
115         super.onResume();
116         updatePrefs();
117     }
118 
119     @VisibleForTesting
updatePrefs()120     void updatePrefs() {
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));
126         } else {
127             mBillingCycle.setSummary(null);
128         }
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);
134         } else {
135             mDataWarning.setSummary(null);
136             mDataWarning.setEnabled(false);
137             mEnableDataWarning.setChecked(false);
138         }
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);
144         } else {
145             mDataLimit.setSummary(null);
146             mDataLimit.setEnabled(false);
147             mEnableDataLimit.setChecked(false);
148         }
149     }
150 
151     @Override
onPreferenceTreeClick(Preference preference)152     public boolean onPreferenceTreeClick(Preference preference) {
153         if (preference == mBillingCycle) {
154             CycleEditorFragment.show(this);
155             return true;
156         } else if (preference == mDataWarning) {
157             BytesEditorFragment.show(this, false);
158             return true;
159         } else if (preference == mDataLimit) {
160             BytesEditorFragment.show(this, true);
161             return true;
162         }
163         return super.onPreferenceTreeClick(preference);
164     }
165 
166     @Override
onPreferenceChange(Preference preference, Object newValue)167     public boolean onPreferenceChange(Preference preference, Object newValue) {
168         if (mEnableDataLimit == preference) {
169             boolean enabled = (Boolean) newValue;
170             if (!enabled) {
171                 setPolicyLimitBytes(LIMIT_DISABLED);
172                 return true;
173             }
174             ConfirmLimitFragment.show(this);
175             // This preference is enabled / disabled by ConfirmLimitFragment.
176             return false;
177         } else if (mEnableDataWarning == preference) {
178             boolean enabled = (Boolean) newValue;
179             if (enabled) {
180                 setPolicyWarningBytes(mDataUsageController.getDefaultWarningLevel());
181             } else {
182                 setPolicyWarningBytes(WARNING_DISABLED);
183             }
184             return true;
185         }
186         return false;
187     }
188 
189     @Override
getMetricsCategory()190     public int getMetricsCategory() {
191         return MetricsEvent.BILLING_CYCLE;
192     }
193 
194     @VisibleForTesting
setPolicyLimitBytes(long limitBytes)195     void setPolicyLimitBytes(long limitBytes) {
196         if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
197         services.mPolicyEditor.setPolicyLimitBytes(mNetworkTemplate, limitBytes);
198         updatePrefs();
199     }
200 
setPolicyWarningBytes(long warningBytes)201     private void setPolicyWarningBytes(long warningBytes) {
202         if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
203         services.mPolicyEditor.setPolicyWarningBytes(mNetworkTemplate, warningBytes);
204         updatePrefs();
205     }
206 
207     @Override
getNetworkPolicyEditor()208     public NetworkPolicyEditor getNetworkPolicyEditor() {
209         return services.mPolicyEditor;
210     }
211 
212     @Override
getNetworkTemplate()213     public NetworkTemplate getNetworkTemplate() {
214         return mNetworkTemplate;
215     }
216 
217     @Override
updateDataUsage()218     public void updateDataUsage() {
219         updatePrefs();
220     }
221 
222     /**
223      * Dialog to edit {@link NetworkPolicy#warningBytes}.
224      */
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";
229         private View mView;
230 
show(DataUsageEditController parent, boolean isLimit)231         public static void show(DataUsageEditController parent, boolean isLimit) {
232             if (!(parent instanceof Fragment)) {
233                 return;
234             }
235             Fragment targetFragment = (Fragment) parent;
236             if (!targetFragment.isAdded()) {
237                 return;
238             }
239 
240             final Bundle args = new Bundle();
241             args.putParcelable(EXTRA_TEMPLATE, parent.getNetworkTemplate());
242             args.putBoolean(EXTRA_LIMIT, isLimit);
243 
244             final BytesEditorFragment dialog = new BytesEditorFragment();
245             dialog.setArguments(args);
246             dialog.setTargetFragment(targetFragment, 0);
247             dialog.show(targetFragment.getFragmentManager(), TAG_WARNING_EDITOR);
248         }
249 
250         @Override
onCreateDialog(Bundle savedInstanceState)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)
261                     .setView(mView)
262                     .setPositiveButton(R.string.data_usage_cycle_editor_positive, this)
263                     .create();
264         }
265 
setupPicker(EditText bytesPicker, Spinner type)266         private void setupPicker(EditText bytesPicker, Spinner type) {
267             final DataUsageEditController target = (DataUsageEditController) getTargetFragment();
268             final NetworkPolicyEditor editor = target.getNetworkPolicyEditor();
269 
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;
275 
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());
280 
281                 type.setSelection(1);
282             } else {
283                 final String bytesText = formatText(bytes / (float) MIB_IN_BYTES);
284                 bytesPicker.setText(bytesText);
285                 bytesPicker.setSelection(0, bytesText.length());
286 
287                 type.setSelection(0);
288             }
289         }
290 
formatText(float v)291         private String formatText(float v) {
292             v = Math.round(v * 100) / 100f;
293             return String.valueOf(v);
294         }
295 
296         @Override
onClick(DialogInterface dialog, int which)297         public void onClick(DialogInterface dialog, int which) {
298             if (which != DialogInterface.BUTTON_POSITIVE) {
299                 return;
300             }
301             final DataUsageEditController target = (DataUsageEditController) getTargetFragment();
302             final NetworkPolicyEditor editor = target.getNetworkPolicyEditor();
303 
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);
308 
309             String bytesString = bytesField.getText().toString();
310             if (bytesString.isEmpty() || bytesString.equals(".")) {
311                 bytesString = "0";
312             }
313             final long bytes = (long) (Float.valueOf(bytesString)
314                     * (spinner.getSelectedItemPosition() == 0 ? MIB_IN_BYTES : GIB_IN_BYTES));
315 
316             // to fix the overflow problem
317             final long correctedBytes = Math.min(MAX_DATA_LIMIT_BYTES, bytes);
318             if (isLimit) {
319                 editor.setPolicyLimitBytes(template, correctedBytes);
320             } else {
321                 editor.setPolicyWarningBytes(template, correctedBytes);
322             }
323             target.updateDataUsage();
324         }
325 
326         @Override
getMetricsCategory()327         public int getMetricsCategory() {
328             return MetricsEvent.DIALOG_BILLING_BYTE_LIMIT;
329         }
330     }
331 
332     /**
333      * Dialog to edit {@link NetworkPolicy}.
334      */
335     public static class CycleEditorFragment extends InstrumentedDialogFragment implements
336             DialogInterface.OnClickListener {
337         private static final String EXTRA_TEMPLATE = "template";
338         private NumberPicker mCycleDayPicker;
339 
show(BillingCycleSettings parent)340         public static void show(BillingCycleSettings parent) {
341             if (!parent.isAdded()) return;
342 
343             final Bundle args = new Bundle();
344             args.putParcelable(EXTRA_TEMPLATE, parent.mNetworkTemplate);
345 
346             final CycleEditorFragment dialog = new CycleEditorFragment();
347             dialog.setArguments(args);
348             dialog.setTargetFragment(parent, 0);
349             dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
350         }
351 
352         @Override
getMetricsCategory()353         public int getMetricsCategory() {
354             return MetricsEvent.DIALOG_BILLING_CYCLE;
355         }
356 
357         @Override
onCreateDialog(Bundle savedInstanceState)358         public Dialog onCreateDialog(Bundle savedInstanceState) {
359             final Context context = getActivity();
360             final DataUsageEditController target = (DataUsageEditController) getTargetFragment();
361             final NetworkPolicyEditor editor = target.getNetworkPolicyEditor();
362 
363             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
364             final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
365 
366             final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
367             mCycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
368 
369             final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
370             final int cycleDay = editor.getPolicyCycleDay(template);
371 
372             mCycleDayPicker.setMinValue(1);
373             mCycleDayPicker.setMaxValue(31);
374             mCycleDayPicker.setValue(cycleDay);
375             mCycleDayPicker.setWrapSelectorWheel(true);
376 
377             return builder.setTitle(R.string.data_usage_cycle_editor_title)
378                     .setView(view)
379                     .setPositiveButton(R.string.data_usage_cycle_editor_positive, this)
380                     .create();
381         }
382 
383         @Override
onClick(DialogInterface dialog, int which)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();
388 
389             // clear focus to finish pending text edits
390             mCycleDayPicker.clearFocus();
391 
392             final int cycleDay = mCycleDayPicker.getValue();
393             final String cycleTimezone = new Time().timezone;
394             editor.setPolicyCycleDay(template, cycleDay, cycleTimezone);
395             target.updateDataUsage();
396         }
397     }
398 
399     /**
400      * Dialog to request user confirmation before setting
401      * {@link NetworkPolicy#limitBytes}.
402      */
403     public static class ConfirmLimitFragment extends InstrumentedDialogFragment implements
404             DialogInterface.OnClickListener {
405         @VisibleForTesting static final String EXTRA_LIMIT_BYTES = "limitBytes";
406         public static final float FLOAT = 1.2f;
407 
show(BillingCycleSettings parent)408         public static void show(BillingCycleSettings parent) {
409             if (!parent.isAdded()) return;
410 
411             final NetworkPolicy policy = parent.services.mPolicyEditor
412                     .getPolicy(parent.mNetworkTemplate);
413             if (policy == null) return;
414 
415             final Resources res = parent.getResources();
416             final long minLimitBytes = (long) (policy.warningBytes * FLOAT);
417             final long limitBytes;
418 
419             // TODO: customize default limits based on network template
420             limitBytes = Math.max(5 * GIB_IN_BYTES, minLimitBytes);
421 
422             final Bundle args = new Bundle();
423             args.putLong(EXTRA_LIMIT_BYTES, limitBytes);
424 
425             final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
426             dialog.setArguments(args);
427             dialog.setTargetFragment(parent, 0);
428             dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
429         }
430 
431         @Override
getMetricsCategory()432         public int getMetricsCategory() {
433             return MetricsEvent.DIALOG_BILLING_CONFIRM_LIMIT;
434         }
435 
436         @Override
onCreateDialog(Bundle savedInstanceState)437         public Dialog onCreateDialog(Bundle savedInstanceState) {
438             final Context context = getActivity();
439 
440             return new AlertDialog.Builder(context)
441                     .setTitle(R.string.data_usage_limit_dialog_title)
442                     .setMessage(R.string.data_usage_limit_dialog_mobile)
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