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