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 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 86 public void onResume() { 87 super.onResume(); 88 updatePrefs(); 89 } 90 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(), 96 policy != null 97 ? policy.warningBytes 98 : mDataUsageController.getDefaultWarningLevel())); 99 if (policy != null && policy.limitBytes != LIMIT_DISABLED) { 100 mDataLimit.setSummary(Formatter.formatFileSize(getContext(), policy.limitBytes)); 101 mDataLimit.setEnabled(true); 102 mEnableDataLimit.setChecked(true); 103 } else { 104 mDataLimit.setSummary(null); 105 mDataLimit.setEnabled(false); 106 mEnableDataLimit.setChecked(false); 107 } 108 } 109 110 @Override 111 public boolean onPreferenceTreeClick(Preference preference) { 112 if (preference == mBillingCycle) { 113 CycleEditorFragment.show(this); 114 return true; 115 } else if (preference == mDataWarning) { 116 BytesEditorFragment.show(this, false); 117 return true; 118 } else if (preference == mDataLimit) { 119 BytesEditorFragment.show(this, true); 120 return true; 121 } 122 return super.onPreferenceTreeClick(preference); 123 } 124 125 @Override 126 public boolean onPreferenceChange(Preference preference, Object newValue) { 127 if (mEnableDataLimit == preference) { 128 boolean enabled = (Boolean) newValue; 129 if (enabled) { 130 ConfirmLimitFragment.show(this); 131 } else { 132 setPolicyLimitBytes(LIMIT_DISABLED); 133 } 134 return true; 135 } 136 return false; 137 } 138 139 @Override 140 protected int getMetricsCategory() { 141 return MetricsEvent.BILLING_CYCLE; 142 } 143 144 private void setPolicyLimitBytes(long limitBytes) { 145 if (LOGD) Log.d(TAG, "setPolicyLimitBytes()"); 146 services.mPolicyEditor.setPolicyLimitBytes(mNetworkTemplate, limitBytes); 147 updatePrefs(); 148 } 149 150 /** 151 * Dialog to edit {@link NetworkPolicy#warningBytes}. 152 */ 153 public static class BytesEditorFragment extends DialogFragment 154 implements DialogInterface.OnClickListener{ 155 private static final String EXTRA_TEMPLATE = "template"; 156 private static final String EXTRA_LIMIT = "limit"; 157 private View mView; 158 159 public static void show(BillingCycleSettings parent, boolean isLimit) { 160 if (!parent.isAdded()) return; 161 162 final Bundle args = new Bundle(); 163 args.putParcelable(EXTRA_TEMPLATE, parent.mNetworkTemplate); 164 args.putBoolean(EXTRA_LIMIT, isLimit); 165 166 final BytesEditorFragment dialog = new BytesEditorFragment(); 167 dialog.setArguments(args); 168 dialog.setTargetFragment(parent, 0); 169 dialog.show(parent.getFragmentManager(), TAG_WARNING_EDITOR); 170 } 171 172 @Override 173 public Dialog onCreateDialog(Bundle savedInstanceState) { 174 final Context context = getActivity(); 175 176 177 final LayoutInflater dialogInflater = LayoutInflater.from(context); 178 final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT); 179 mView = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false); 180 setupPicker((EditText) mView.findViewById(R.id.bytes), 181 (Spinner) mView.findViewById(R.id.size_spinner)); 182 return new AlertDialog.Builder(context) 183 .setTitle(isLimit ? R.string.data_usage_limit_editor_title 184 : R.string.data_usage_warning_editor_title) 185 .setView(mView) 186 .setPositiveButton(R.string.data_usage_cycle_editor_positive, this) 187 .create(); 188 } 189 190 private void setupPicker(EditText bytesPicker, Spinner type) { 191 final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment(); 192 final NetworkPolicyEditor editor = target.services.mPolicyEditor; 193 194 final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); 195 final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT); 196 final long bytes = isLimit ? editor.getPolicyLimitBytes(template) 197 : editor.getPolicyWarningBytes(template); 198 final long limitDisabled = isLimit ? LIMIT_DISABLED : WARNING_DISABLED; 199 200 if (bytes > 1.5f * GB_IN_BYTES) { 201 bytesPicker.setText(formatText(bytes / (float) GB_IN_BYTES)); 202 type.setSelection(1); 203 } else { 204 bytesPicker.setText(formatText(bytes / (float) MB_IN_BYTES)); 205 type.setSelection(0); 206 } 207 } 208 209 private String formatText(float v) { 210 v = Math.round(v * 100) / 100f; 211 return String.valueOf(v); 212 } 213 214 @Override 215 public void onClick(DialogInterface dialog, int which) { 216 if (which != DialogInterface.BUTTON_POSITIVE) { 217 return; 218 } 219 final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment(); 220 final NetworkPolicyEditor editor = target.services.mPolicyEditor; 221 222 final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); 223 final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT); 224 EditText bytesField = (EditText) mView.findViewById(R.id.bytes); 225 Spinner spinner = (Spinner) mView.findViewById(R.id.size_spinner); 226 227 String bytesString = bytesField.getText().toString(); 228 if (bytesString.isEmpty()) { 229 bytesString = "0"; 230 } 231 final long bytes = (long) (Float.valueOf(bytesString) 232 * (spinner.getSelectedItemPosition() == 0 ? MB_IN_BYTES : GB_IN_BYTES)); 233 if (isLimit) { 234 editor.setPolicyLimitBytes(template, bytes); 235 } else { 236 editor.setPolicyWarningBytes(template, bytes); 237 } 238 target.updatePrefs(); 239 } 240 } 241 242 /** 243 * Dialog to edit {@link NetworkPolicy#cycleDay}. 244 */ 245 public static class CycleEditorFragment extends DialogFragment implements 246 DialogInterface.OnClickListener{ 247 private static final String EXTRA_TEMPLATE = "template"; 248 private NumberPicker mCycleDayPicker; 249 250 public static void show(BillingCycleSettings parent) { 251 if (!parent.isAdded()) return; 252 253 final Bundle args = new Bundle(); 254 args.putParcelable(EXTRA_TEMPLATE, parent.mNetworkTemplate); 255 256 final CycleEditorFragment dialog = new CycleEditorFragment(); 257 dialog.setArguments(args); 258 dialog.setTargetFragment(parent, 0); 259 dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR); 260 } 261 262 @Override 263 public Dialog onCreateDialog(Bundle savedInstanceState) { 264 final Context context = getActivity(); 265 final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment(); 266 final NetworkPolicyEditor editor = target.services.mPolicyEditor; 267 268 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 269 final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); 270 271 final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false); 272 mCycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day); 273 274 final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); 275 final int cycleDay = editor.getPolicyCycleDay(template); 276 277 mCycleDayPicker.setMinValue(1); 278 mCycleDayPicker.setMaxValue(31); 279 mCycleDayPicker.setValue(cycleDay); 280 mCycleDayPicker.setWrapSelectorWheel(true); 281 282 return builder.setTitle(R.string.data_usage_cycle_editor_title) 283 .setView(view) 284 .setPositiveButton(R.string.data_usage_cycle_editor_positive, this) 285 .create(); 286 } 287 288 @Override 289 public void onClick(DialogInterface dialog, int which) { 290 final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); 291 final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment(); 292 final NetworkPolicyEditor editor = target.services.mPolicyEditor; 293 294 // clear focus to finish pending text edits 295 mCycleDayPicker.clearFocus(); 296 297 final int cycleDay = mCycleDayPicker.getValue(); 298 final String cycleTimezone = new Time().timezone; 299 editor.setPolicyCycleDay(template, cycleDay, cycleTimezone); 300 target.updatePrefs(); 301 } 302 } 303 304 /** 305 * Dialog to request user confirmation before setting 306 * {@link NetworkPolicy#limitBytes}. 307 */ 308 public static class ConfirmLimitFragment extends DialogFragment implements 309 DialogInterface.OnClickListener{ 310 private static final String EXTRA_MESSAGE = "message"; 311 private static final String EXTRA_LIMIT_BYTES = "limitBytes"; 312 public static final float FLOAT = 1.2f; 313 314 public static void show(BillingCycleSettings parent) { 315 if (!parent.isAdded()) return; 316 317 final NetworkPolicy policy = parent.services.mPolicyEditor 318 .getPolicy(parent.mNetworkTemplate); 319 if (policy == null) return; 320 321 final Resources res = parent.getResources(); 322 final CharSequence message; 323 final long minLimitBytes = (long) (policy.warningBytes * FLOAT); 324 final long limitBytes; 325 326 // TODO: customize default limits based on network template 327 message = res.getString(R.string.data_usage_limit_dialog_mobile); 328 limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes); 329 330 final Bundle args = new Bundle(); 331 args.putCharSequence(EXTRA_MESSAGE, message); 332 args.putLong(EXTRA_LIMIT_BYTES, limitBytes); 333 334 final ConfirmLimitFragment dialog = new ConfirmLimitFragment(); 335 dialog.setArguments(args); 336 dialog.setTargetFragment(parent, 0); 337 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT); 338 } 339 340 @Override 341 public Dialog onCreateDialog(Bundle savedInstanceState) { 342 final Context context = getActivity(); 343 344 final CharSequence message = getArguments().getCharSequence(EXTRA_MESSAGE); 345 346 return new AlertDialog.Builder(context) 347 .setTitle(R.string.data_usage_limit_dialog_title) 348 .setMessage(message) 349 .setPositiveButton(android.R.string.ok, this) 350 .setNegativeButton(android.R.string.cancel, null) 351 .create(); 352 } 353 354 @Override 355 public void onClick(DialogInterface dialog, int which) { 356 if (which != DialogInterface.BUTTON_POSITIVE) return; 357 final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES); 358 final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment(); 359 if (target != null) { 360 target.setPolicyLimitBytes(limitBytes); 361 } 362 } 363 } 364 } 365