Home | History | Annotate | Download | only in datausage
      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         @VisibleForTesting static final String EXTRA_LIMIT_BYTES = "limitBytes";
    406         public static final float FLOAT = 1.2f;
    407 
    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
    432         public int getMetricsCategory() {
    433             return MetricsEvent.DIALOG_BILLING_CONFIRM_LIMIT;
    434         }
    435 
    436         @Override
    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
    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