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