OSDN Git Service

b82e3becef4c7530ec4374854b2f6e6f01aeca13
[android-x86/packages-apps-Settings.git] / src / com / android / settings / datausage / BillingCycleSettings.java
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
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
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
114     public void onResume() {
115         super.onResume();
116         updatePrefs();
117     }
118
119     @VisibleForTesting
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
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
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
190     public int getMetricsCategory() {
191         return MetricsEvent.BILLING_CYCLE;
192     }
193
194     @VisibleForTesting
195     void setPolicyLimitBytes(long limitBytes) {
196         if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
197         services.mPolicyEditor.setPolicyLimitBytes(mNetworkTemplate, limitBytes);
198         updatePrefs();
199     }
200
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
208     public NetworkPolicyEditor getNetworkPolicyEditor() {
209         return services.mPolicyEditor;
210     }
211
212     @Override
213     public NetworkTemplate getNetworkTemplate() {
214         return mNetworkTemplate;
215     }
216
217     @Override
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
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
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
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
291         private String formatText(float v) {
292             v = Math.round(v * 100) / 100f;
293             return String.valueOf(v);
294         }
295
296         @Override
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
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
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
353         public int getMetricsCategory() {
354             return MetricsEvent.DIALOG_BILLING_CYCLE;
355         }
356
357         @Override
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
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         private static final String EXTRA_MESSAGE = "message";
406         @VisibleForTesting static final String EXTRA_LIMIT_BYTES = "limitBytes";
407         public static final float FLOAT = 1.2f;
408
409         public static void show(BillingCycleSettings parent) {
410             if (!parent.isAdded()) return;
411
412             final NetworkPolicy policy = parent.services.mPolicyEditor
413                     .getPolicy(parent.mNetworkTemplate);
414             if (policy == null) return;
415
416             final Resources res = parent.getResources();
417             final CharSequence message;
418             final long minLimitBytes = (long) (policy.warningBytes * FLOAT);
419             final long limitBytes;
420
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);
424
425             final Bundle args = new Bundle();
426             args.putCharSequence(EXTRA_MESSAGE, message);
427             args.putLong(EXTRA_LIMIT_BYTES, limitBytes);
428
429             final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
430             dialog.setArguments(args);
431             dialog.setTargetFragment(parent, 0);
432             dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
433         }
434
435         @Override
436         public int getMetricsCategory() {
437             return MetricsEvent.DIALOG_BILLING_CONFIRM_LIMIT;
438         }
439
440         @Override
441         public Dialog onCreateDialog(Bundle savedInstanceState) {
442             final Context context = getActivity();
443
444             final CharSequence message = getArguments().getCharSequence(EXTRA_MESSAGE);
445
446             return new AlertDialog.Builder(context)
447                     .setTitle(R.string.data_usage_limit_dialog_title)
448                     .setMessage(message)
449                     .setPositiveButton(android.R.string.ok, this)
450                     .setNegativeButton(android.R.string.cancel, null)
451                     .create();
452         }
453
454         @Override
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);
461             }
462             target.getPreferenceManager().getSharedPreferences().edit()
463                     .putBoolean(KEY_SET_DATA_LIMIT, true).apply();
464         }
465     }
466 }