This used to by MByte and GByte, not used in other places in Settings, and not align with the data summary on the same page. Unify to MB and GB to align with other places in Settings and the data summary on the same page. Fix: 277900792 Test: Visual Test: Unit test Change-Id: I9f8ceac470aede135b7921c391c063ba87da661e
536 lines
21 KiB
Java
536 lines
21 KiB
Java
/*
|
|
* Copyright (C) 2016 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software distributed under the
|
|
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the specific language governing
|
|
* permissions and limitations under the License.
|
|
*/
|
|
|
|
package com.android.settings.datausage;
|
|
|
|
import static android.net.NetworkPolicy.LIMIT_DISABLED;
|
|
import static android.net.NetworkPolicy.WARNING_DISABLED;
|
|
|
|
import android.app.Dialog;
|
|
import android.app.settings.SettingsEnums;
|
|
import android.content.Context;
|
|
import android.content.DialogInterface;
|
|
import android.content.res.Resources;
|
|
import android.net.NetworkPolicy;
|
|
import android.net.NetworkTemplate;
|
|
import android.os.Bundle;
|
|
import android.provider.Settings;
|
|
import android.text.method.NumberKeyListener;
|
|
import android.util.Log;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.inputmethod.EditorInfo;
|
|
import android.widget.ArrayAdapter;
|
|
import android.widget.EditText;
|
|
import android.widget.NumberPicker;
|
|
import android.widget.Spinner;
|
|
|
|
import androidx.annotation.VisibleForTesting;
|
|
import androidx.appcompat.app.AlertDialog;
|
|
import androidx.fragment.app.Fragment;
|
|
import androidx.preference.Preference;
|
|
import androidx.preference.SwitchPreference;
|
|
|
|
import com.android.settings.R;
|
|
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
|
import com.android.settings.network.SubscriptionUtil;
|
|
import com.android.settings.network.telephony.MobileNetworkUtils;
|
|
import com.android.settings.search.BaseSearchIndexProvider;
|
|
import com.android.settingslib.NetworkPolicyEditor;
|
|
import com.android.settingslib.net.DataUsageController;
|
|
import com.android.settingslib.search.SearchIndexable;
|
|
|
|
import java.text.NumberFormat;
|
|
import java.text.ParseException;
|
|
import java.util.Optional;
|
|
import java.util.TimeZone;
|
|
|
|
@SearchIndexable
|
|
public class BillingCycleSettings extends DataUsageBaseFragment implements
|
|
Preference.OnPreferenceChangeListener, DataUsageEditController {
|
|
|
|
private static final String TAG = "BillingCycleSettings";
|
|
private static final boolean LOGD = false;
|
|
public static final long MIB_IN_BYTES = 1024 * 1024;
|
|
public static final long GIB_IN_BYTES = MIB_IN_BYTES * 1024;
|
|
|
|
private static final long MAX_DATA_LIMIT_BYTES = 50000 * GIB_IN_BYTES;
|
|
|
|
private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
|
|
private static final String TAG_CYCLE_EDITOR = "cycleEditor";
|
|
private static final String TAG_WARNING_EDITOR = "warningEditor";
|
|
|
|
private static final String KEY_BILLING_CYCLE = "billing_cycle";
|
|
private static final String KEY_SET_DATA_WARNING = "set_data_warning";
|
|
private static final String KEY_DATA_WARNING = "data_warning";
|
|
@VisibleForTesting
|
|
static final String KEY_SET_DATA_LIMIT = "set_data_limit";
|
|
private static final String KEY_DATA_LIMIT = "data_limit";
|
|
|
|
@VisibleForTesting
|
|
NetworkTemplate mNetworkTemplate;
|
|
private Preference mBillingCycle;
|
|
private Preference mDataWarning;
|
|
private SwitchPreference mEnableDataWarning;
|
|
private SwitchPreference mEnableDataLimit;
|
|
private Preference mDataLimit;
|
|
private DataUsageController mDataUsageController;
|
|
|
|
@VisibleForTesting
|
|
void setUpForTest(NetworkPolicyEditor policyEditor,
|
|
Preference billingCycle,
|
|
Preference dataLimit,
|
|
Preference dataWarning,
|
|
SwitchPreference enableLimit,
|
|
SwitchPreference enableWarning) {
|
|
services.mPolicyEditor = policyEditor;
|
|
mBillingCycle = billingCycle;
|
|
mDataLimit = dataLimit;
|
|
mDataWarning = dataWarning;
|
|
mEnableDataLimit = enableLimit;
|
|
mEnableDataWarning = enableWarning;
|
|
}
|
|
|
|
@Override
|
|
public void onCreate(Bundle icicle) {
|
|
super.onCreate(icicle);
|
|
|
|
final Context context = getContext();
|
|
if (!SubscriptionUtil.isSimHardwareVisible(context)) {
|
|
finish();
|
|
return;
|
|
}
|
|
mDataUsageController = new DataUsageController(context);
|
|
|
|
Bundle args = getArguments();
|
|
mNetworkTemplate = args.getParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE);
|
|
if (mNetworkTemplate == null && getIntent() != null) {
|
|
mNetworkTemplate = getIntent().getParcelableExtra(Settings.EXTRA_NETWORK_TEMPLATE);
|
|
}
|
|
|
|
if (mNetworkTemplate == null) {
|
|
Optional<NetworkTemplate> mobileNetworkTemplateFromSim =
|
|
DataUsageUtils.getMobileNetworkTemplateFromSubId(context, getIntent());
|
|
if (mobileNetworkTemplateFromSim.isPresent()) {
|
|
mNetworkTemplate = mobileNetworkTemplateFromSim.get();
|
|
}
|
|
}
|
|
|
|
if (mNetworkTemplate == null) {
|
|
mNetworkTemplate = DataUsageUtils.getDefaultTemplate(context,
|
|
DataUsageUtils.getDefaultSubscriptionId(context));
|
|
}
|
|
|
|
mBillingCycle = findPreference(KEY_BILLING_CYCLE);
|
|
mEnableDataWarning = (SwitchPreference) findPreference(KEY_SET_DATA_WARNING);
|
|
mEnableDataWarning.setOnPreferenceChangeListener(this);
|
|
mDataWarning = findPreference(KEY_DATA_WARNING);
|
|
mEnableDataLimit = (SwitchPreference) findPreference(KEY_SET_DATA_LIMIT);
|
|
mEnableDataLimit.setOnPreferenceChangeListener(this);
|
|
mDataLimit = findPreference(KEY_DATA_LIMIT);
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
super.onResume();
|
|
updatePrefs();
|
|
}
|
|
|
|
@VisibleForTesting
|
|
void updatePrefs() {
|
|
mBillingCycle.setSummary(null);
|
|
final long warningBytes = services.mPolicyEditor.getPolicyWarningBytes(mNetworkTemplate);
|
|
if (warningBytes != WARNING_DISABLED) {
|
|
mDataWarning.setSummary(DataUsageUtils.formatDataUsage(getContext(), warningBytes));
|
|
mDataWarning.setEnabled(true);
|
|
mEnableDataWarning.setChecked(true);
|
|
} else {
|
|
mDataWarning.setSummary(null);
|
|
mDataWarning.setEnabled(false);
|
|
mEnableDataWarning.setChecked(false);
|
|
}
|
|
final long limitBytes = services.mPolicyEditor.getPolicyLimitBytes(mNetworkTemplate);
|
|
if (limitBytes != LIMIT_DISABLED) {
|
|
mDataLimit.setSummary(DataUsageUtils.formatDataUsage(getContext(), limitBytes));
|
|
mDataLimit.setEnabled(true);
|
|
mEnableDataLimit.setChecked(true);
|
|
} else {
|
|
mDataLimit.setSummary(null);
|
|
mDataLimit.setEnabled(false);
|
|
mEnableDataLimit.setChecked(false);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onPreferenceTreeClick(Preference preference) {
|
|
if (preference == mBillingCycle) {
|
|
writePreferenceClickMetric(preference);
|
|
CycleEditorFragment.show(this);
|
|
return true;
|
|
} else if (preference == mDataWarning) {
|
|
writePreferenceClickMetric(preference);
|
|
BytesEditorFragment.show(this, false);
|
|
return true;
|
|
} else if (preference == mDataLimit) {
|
|
writePreferenceClickMetric(preference);
|
|
BytesEditorFragment.show(this, true);
|
|
return true;
|
|
}
|
|
return super.onPreferenceTreeClick(preference);
|
|
}
|
|
|
|
@Override
|
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
|
if (mEnableDataLimit == preference) {
|
|
boolean enabled = (Boolean) newValue;
|
|
if (!enabled) {
|
|
setPolicyLimitBytes(LIMIT_DISABLED);
|
|
return true;
|
|
}
|
|
ConfirmLimitFragment.show(this);
|
|
// This preference is enabled / disabled by ConfirmLimitFragment.
|
|
return false;
|
|
} else if (mEnableDataWarning == preference) {
|
|
boolean enabled = (Boolean) newValue;
|
|
if (enabled) {
|
|
setPolicyWarningBytes(mDataUsageController.getDefaultWarningLevel());
|
|
} else {
|
|
setPolicyWarningBytes(WARNING_DISABLED);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public int getMetricsCategory() {
|
|
return SettingsEnums.BILLING_CYCLE;
|
|
}
|
|
|
|
@Override
|
|
protected int getPreferenceScreenResId() {
|
|
return R.xml.billing_cycle;
|
|
}
|
|
|
|
@Override
|
|
protected String getLogTag() {
|
|
return TAG;
|
|
}
|
|
|
|
@VisibleForTesting
|
|
void setPolicyLimitBytes(long limitBytes) {
|
|
if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
|
|
services.mPolicyEditor.setPolicyLimitBytes(mNetworkTemplate, limitBytes);
|
|
updatePrefs();
|
|
}
|
|
|
|
private void setPolicyWarningBytes(long warningBytes) {
|
|
if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
|
|
services.mPolicyEditor.setPolicyWarningBytes(mNetworkTemplate, warningBytes);
|
|
updatePrefs();
|
|
}
|
|
|
|
@Override
|
|
public NetworkPolicyEditor getNetworkPolicyEditor() {
|
|
return services.mPolicyEditor;
|
|
}
|
|
|
|
@Override
|
|
public NetworkTemplate getNetworkTemplate() {
|
|
return mNetworkTemplate;
|
|
}
|
|
|
|
@Override
|
|
public void updateDataUsage() {
|
|
updatePrefs();
|
|
}
|
|
|
|
/**
|
|
* Dialog to edit {@link NetworkPolicy#warningBytes}.
|
|
*/
|
|
public static class BytesEditorFragment extends InstrumentedDialogFragment
|
|
implements DialogInterface.OnClickListener {
|
|
private static final String EXTRA_TEMPLATE = "template";
|
|
private static final String EXTRA_LIMIT = "limit";
|
|
private View mView;
|
|
|
|
public static void show(DataUsageEditController parent, boolean isLimit) {
|
|
if (!(parent instanceof Fragment)) {
|
|
return;
|
|
}
|
|
Fragment targetFragment = (Fragment) parent;
|
|
if (!targetFragment.isAdded()) {
|
|
return;
|
|
}
|
|
|
|
final Bundle args = new Bundle();
|
|
args.putParcelable(EXTRA_TEMPLATE, parent.getNetworkTemplate());
|
|
args.putBoolean(EXTRA_LIMIT, isLimit);
|
|
|
|
final BytesEditorFragment dialog = new BytesEditorFragment();
|
|
dialog.setArguments(args);
|
|
dialog.setTargetFragment(targetFragment, 0);
|
|
dialog.show(targetFragment.getFragmentManager(), TAG_WARNING_EDITOR);
|
|
}
|
|
|
|
@Override
|
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
final Context context = getActivity();
|
|
final LayoutInflater dialogInflater = LayoutInflater.from(context);
|
|
final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT);
|
|
mView = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
|
|
setupPicker((EditText) mView.findViewById(R.id.bytes),
|
|
(Spinner) mView.findViewById(R.id.size_spinner));
|
|
Dialog dialog = new AlertDialog.Builder(context)
|
|
.setTitle(isLimit ? R.string.data_usage_limit_editor_title
|
|
: R.string.data_usage_warning_editor_title)
|
|
.setView(mView)
|
|
.setPositiveButton(R.string.data_usage_cycle_editor_positive, this)
|
|
.create();
|
|
dialog.setCanceledOnTouchOutside(false);
|
|
return dialog;
|
|
}
|
|
|
|
private void setupPicker(EditText bytesPicker, Spinner type) {
|
|
final DataUsageEditController target = (DataUsageEditController) getTargetFragment();
|
|
final NetworkPolicyEditor editor = target.getNetworkPolicyEditor();
|
|
|
|
bytesPicker.setKeyListener(new NumberKeyListener() {
|
|
protected char[] getAcceptedChars() {
|
|
return new char [] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
',', '.'};
|
|
}
|
|
public int getInputType() {
|
|
return EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
|
|
}
|
|
});
|
|
|
|
final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
|
|
final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT);
|
|
final long bytes = isLimit ? editor.getPolicyLimitBytes(template)
|
|
: editor.getPolicyWarningBytes(template);
|
|
|
|
final String[] unitNames = new String[] {
|
|
DataUsageFormatter.INSTANCE.getBytesDisplayUnit(getResources(), MIB_IN_BYTES),
|
|
DataUsageFormatter.INSTANCE.getBytesDisplayUnit(getResources(), GIB_IN_BYTES),
|
|
};
|
|
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
|
|
getContext(), android.R.layout.simple_spinner_item, unitNames);
|
|
type.setAdapter(adapter);
|
|
|
|
final boolean unitInGigaBytes = (bytes > 1.5f * GIB_IN_BYTES);
|
|
final String bytesText = formatText(bytes,
|
|
unitInGigaBytes ? GIB_IN_BYTES : MIB_IN_BYTES);
|
|
bytesPicker.setText(bytesText);
|
|
bytesPicker.setSelection(0, bytesText.length());
|
|
|
|
type.setSelection(unitInGigaBytes ? 1 : 0);
|
|
}
|
|
|
|
private String formatText(double v, double unitInBytes) {
|
|
final NumberFormat formatter = NumberFormat.getNumberInstance();
|
|
formatter.setMaximumFractionDigits(2);
|
|
return formatter.format((double) (v / unitInBytes));
|
|
}
|
|
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
if (which != DialogInterface.BUTTON_POSITIVE) {
|
|
return;
|
|
}
|
|
final DataUsageEditController target = (DataUsageEditController) getTargetFragment();
|
|
final NetworkPolicyEditor editor = target.getNetworkPolicyEditor();
|
|
|
|
final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
|
|
final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT);
|
|
final EditText bytesField = (EditText) mView.findViewById(R.id.bytes);
|
|
final Spinner spinner = (Spinner) mView.findViewById(R.id.size_spinner);
|
|
|
|
final String bytesString = bytesField.getText().toString();
|
|
|
|
final NumberFormat formatter = NumberFormat.getNumberInstance();
|
|
Number number = null;
|
|
try {
|
|
number = formatter.parse(bytesString);
|
|
} catch (ParseException ex) {
|
|
}
|
|
long bytes = 0L;
|
|
if (number != null) {
|
|
bytes = (long) (number.floatValue()
|
|
* (spinner.getSelectedItemPosition() == 0 ? MIB_IN_BYTES : GIB_IN_BYTES));
|
|
}
|
|
|
|
// to fix the overflow problem
|
|
final long correctedBytes = Math.min(MAX_DATA_LIMIT_BYTES, bytes);
|
|
if (isLimit) {
|
|
editor.setPolicyLimitBytes(template, correctedBytes);
|
|
} else {
|
|
editor.setPolicyWarningBytes(template, correctedBytes);
|
|
}
|
|
target.updateDataUsage();
|
|
}
|
|
|
|
@Override
|
|
public int getMetricsCategory() {
|
|
return SettingsEnums.DIALOG_BILLING_BYTE_LIMIT;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dialog to edit {@link NetworkPolicy}.
|
|
*/
|
|
public static class CycleEditorFragment extends InstrumentedDialogFragment implements
|
|
DialogInterface.OnClickListener {
|
|
private static final String EXTRA_TEMPLATE = "template";
|
|
private NumberPicker mCycleDayPicker;
|
|
|
|
public static void show(BillingCycleSettings parent) {
|
|
if (!parent.isAdded()) return;
|
|
|
|
final Bundle args = new Bundle();
|
|
args.putParcelable(EXTRA_TEMPLATE, parent.mNetworkTemplate);
|
|
|
|
final CycleEditorFragment dialog = new CycleEditorFragment();
|
|
dialog.setArguments(args);
|
|
dialog.setTargetFragment(parent, 0);
|
|
dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
|
|
}
|
|
|
|
@Override
|
|
public int getMetricsCategory() {
|
|
return SettingsEnums.DIALOG_BILLING_CYCLE;
|
|
}
|
|
|
|
@Override
|
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
final Context context = getActivity();
|
|
final DataUsageEditController target = (DataUsageEditController) getTargetFragment();
|
|
final NetworkPolicyEditor editor = target.getNetworkPolicyEditor();
|
|
|
|
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
|
final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
|
|
|
|
final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
|
|
mCycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
|
|
|
|
final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
|
|
final int cycleDay = editor.getPolicyCycleDay(template);
|
|
|
|
mCycleDayPicker.setMinValue(1);
|
|
mCycleDayPicker.setMaxValue(31);
|
|
mCycleDayPicker.setValue(cycleDay);
|
|
mCycleDayPicker.setWrapSelectorWheel(true);
|
|
|
|
Dialog dialog = builder.setTitle(R.string.data_usage_cycle_editor_title)
|
|
.setView(view)
|
|
.setPositiveButton(R.string.data_usage_cycle_editor_positive, this)
|
|
.create();
|
|
dialog.setCanceledOnTouchOutside(false);
|
|
return dialog;
|
|
}
|
|
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
|
|
final DataUsageEditController target = (DataUsageEditController) getTargetFragment();
|
|
final NetworkPolicyEditor editor = target.getNetworkPolicyEditor();
|
|
|
|
// clear focus to finish pending text edits
|
|
mCycleDayPicker.clearFocus();
|
|
|
|
final int cycleDay = mCycleDayPicker.getValue();
|
|
final String cycleTimezone = TimeZone.getDefault().getID();
|
|
editor.setPolicyCycleDay(template, cycleDay, cycleTimezone);
|
|
target.updateDataUsage();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dialog to request user confirmation before setting
|
|
* {@link NetworkPolicy#limitBytes}.
|
|
*/
|
|
public static class ConfirmLimitFragment extends InstrumentedDialogFragment implements
|
|
DialogInterface.OnClickListener {
|
|
@VisibleForTesting
|
|
static final String EXTRA_LIMIT_BYTES = "limitBytes";
|
|
public static final float FLOAT = 1.2f;
|
|
|
|
public static void show(BillingCycleSettings parent) {
|
|
if (!parent.isAdded()) return;
|
|
|
|
final NetworkPolicy policy = parent.services.mPolicyEditor
|
|
.getPolicy(parent.mNetworkTemplate);
|
|
if (policy == null) return;
|
|
|
|
final Resources res = parent.getResources();
|
|
final long minLimitBytes = (long) (policy.warningBytes * FLOAT);
|
|
final long limitBytes;
|
|
|
|
// TODO: customize default limits based on network template
|
|
limitBytes = Math.max(5 * GIB_IN_BYTES, minLimitBytes);
|
|
|
|
final Bundle args = new Bundle();
|
|
args.putLong(EXTRA_LIMIT_BYTES, limitBytes);
|
|
|
|
final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
|
|
dialog.setArguments(args);
|
|
dialog.setTargetFragment(parent, 0);
|
|
dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
|
|
}
|
|
|
|
@Override
|
|
public int getMetricsCategory() {
|
|
return SettingsEnums.DIALOG_BILLING_CONFIRM_LIMIT;
|
|
}
|
|
|
|
@Override
|
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
final Context context = getActivity();
|
|
|
|
Dialog dialog = new AlertDialog.Builder(context)
|
|
.setTitle(R.string.data_usage_limit_dialog_title)
|
|
.setMessage(R.string.data_usage_limit_dialog_mobile)
|
|
.setPositiveButton(android.R.string.ok, this)
|
|
.setNegativeButton(android.R.string.cancel, null)
|
|
.create();
|
|
dialog.setCanceledOnTouchOutside(false);
|
|
return dialog;
|
|
}
|
|
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment();
|
|
if (which != DialogInterface.BUTTON_POSITIVE) return;
|
|
final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES);
|
|
if (target != null) {
|
|
target.setPolicyLimitBytes(limitBytes);
|
|
}
|
|
target.getPreferenceManager().getSharedPreferences().edit()
|
|
.putBoolean(KEY_SET_DATA_LIMIT, true).apply();
|
|
}
|
|
}
|
|
|
|
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
|
new BaseSearchIndexProvider(R.xml.billing_cycle) {
|
|
|
|
@Override
|
|
protected boolean isPageSearchEnabled(Context context) {
|
|
return (!MobileNetworkUtils.isMobileNetworkUserRestricted(context))
|
|
&& SubscriptionUtil.isSimHardwareVisible(context)
|
|
&& DataUsageUtils.hasMobileData(context);
|
|
}
|
|
};
|
|
|
|
}
|