From 0103dce92f5f28c5f7eae6d970009b456c39683f Mon Sep 17 00:00:00 2001 From: Ido Ben-Hur Date: Fri, 22 Aug 2025 14:30:54 +0300 Subject: [PATCH] Settings: Add support for daily and weekly data usage cycles [2/2] Change-Id: Ib43ca55f0f7fec5202982f5786176184809ab9f8 --- res/layout/data_usage_cycle_editor.xml | 1 + res/values/evolution_arrays.xml | 13 +++ res/values/evolution_strings.xml | 15 ++++ res/xml/billing_cycle.xml | 7 ++ .../datausage/BillingCycleSettings.java | 86 +++++++++++++++++-- .../datausage/DataUsageSummaryPreference.java | 27 ++++-- 6 files changed, 136 insertions(+), 13 deletions(-) diff --git a/res/layout/data_usage_cycle_editor.xml b/res/layout/data_usage_cycle_editor.xml index ff5fceb8644..22a6742f13e 100644 --- a/res/layout/data_usage_cycle_editor.xml +++ b/res/layout/data_usage_cycle_editor.xml @@ -21,6 +21,7 @@ android:layout_height="wrap_content"> 2 3 + + + + @string/billing_cycle_type_monthly + @string/billing_cycle_type_weekly + @string/billing_cycle_type_daily + + + + 0 + 1 + 2 + diff --git a/res/values/evolution_strings.xml b/res/values/evolution_strings.xml index ec9a54c5faa..935074456c4 100644 --- a/res/values/evolution_strings.xml +++ b/res/values/evolution_strings.xml @@ -369,4 +369,19 @@ Dark mode for third-party apps Forces darker colors in apps lacking dark mode. Some screens may appear off. + + + Mobile data usage cycle type + Monthly + Weekly + Daily + Less than 1 hour left + {count, plural, + =1 {# hour left} + other {# hours left} + } + Usage cycle reset day + Day of each week:\n1 = Monday + Usage cycle reset hour + Hour of each day: diff --git a/res/xml/billing_cycle.xml b/res/xml/billing_cycle.xml index 000abace7f2..c43fc94cc91 100644 --- a/res/xml/billing_cycle.xml +++ b/res/xml/billing_cycle.xml @@ -19,6 +19,13 @@ xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/billing_cycle"> + + diff --git a/src/com/android/settings/datausage/BillingCycleSettings.java b/src/com/android/settings/datausage/BillingCycleSettings.java index 3f240e65c09..eb6e792d00e 100644 --- a/src/com/android/settings/datausage/BillingCycleSettings.java +++ b/src/com/android/settings/datausage/BillingCycleSettings.java @@ -35,10 +35,12 @@ import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.NumberPicker; import android.widget.Spinner; +import android.widget.TextView; import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; +import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.TwoStatePreference; @@ -69,10 +71,15 @@ public class BillingCycleSettings extends DataUsageBaseFragment implements private static final long MAX_DATA_LIMIT_BYTES = 50000 * GIB_IN_BYTES; + public static final int CYCLE_TYPE_MONTHLY = 0; + public static final int CYCLE_TYPE_WEEKLY = 1; + public static final int CYCLE_TYPE_DAILY = 2; + 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_TYPE = "billing_cycle_type"; 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"; @@ -82,6 +89,7 @@ public class BillingCycleSettings extends DataUsageBaseFragment implements @VisibleForTesting NetworkTemplate mNetworkTemplate; + private ListPreference mBillingCycleType; private Preference mBillingCycle; private Preference mDataWarning; private TwoStatePreference mEnableDataWarning; @@ -104,6 +112,18 @@ public class BillingCycleSettings extends DataUsageBaseFragment implements mEnableDataWarning = enableWarning; } + int getBillingCycleType() { + if (services.mPolicyEditor.getPolicyCycleDay(mNetworkTemplate) + != NetworkPolicy.CYCLE_NONE) { + return CYCLE_TYPE_MONTHLY; + } + if (services.mPolicyEditor.getPolicyCycleDayOfWeek(mNetworkTemplate) + != NetworkPolicy.CYCLE_NONE) { + return CYCLE_TYPE_WEEKLY; + } + return CYCLE_TYPE_DAILY; + } + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -133,6 +153,8 @@ public class BillingCycleSettings extends DataUsageBaseFragment implements mNetworkTemplate = NetworkTemplates.INSTANCE.getDefaultTemplate(context); } + mBillingCycleType = (ListPreference) findPreference(KEY_BILLING_CYCLE_TYPE); + mBillingCycleType.setOnPreferenceChangeListener(this); mBillingCycle = findPreference(KEY_BILLING_CYCLE); mEnableDataWarning = (TwoStatePreference) findPreference(KEY_SET_DATA_WARNING); mEnableDataWarning.setOnPreferenceChangeListener(this); @@ -150,6 +172,7 @@ public class BillingCycleSettings extends DataUsageBaseFragment implements @VisibleForTesting void updatePrefs() { + mBillingCycleType.setValue(String.valueOf(getBillingCycleType())); mBillingCycle.setSummary(null); final long warningBytes = services.mPolicyEditor.getPolicyWarningBytes(mNetworkTemplate); DataUsageFormatter dataUsageFormatter = new DataUsageFormatter(requireContext()); @@ -211,6 +234,10 @@ public class BillingCycleSettings extends DataUsageBaseFragment implements setPolicyWarningBytes(WARNING_DISABLED); } return true; + } else if (mBillingCycleType == preference) { + int value = Integer.parseInt((String) newValue); + handleBillingCycleChanged(value); + return true; } return false; } @@ -243,6 +270,28 @@ public class BillingCycleSettings extends DataUsageBaseFragment implements updatePrefs(); } + private void handleBillingCycleChanged(int cycleType) { + if (getBillingCycleType() == cycleType) { + // nothing changed + return; + } + final String cycleTimezone = TimeZone.getDefault().getID(); + final NetworkPolicyEditor editor = services.mPolicyEditor; + + switch (cycleType) { + default: + case CYCLE_TYPE_MONTHLY: + editor.setPolicyCycleDay(mNetworkTemplate, 1, cycleTimezone); + break; + case CYCLE_TYPE_WEEKLY: + editor.setPolicyCycleDayOfWeek(mNetworkTemplate, 1, cycleTimezone); + break; + case CYCLE_TYPE_DAILY: + editor.setPolicyCycleHour(mNetworkTemplate, 0, cycleTimezone); + break; + } + } + @Override public NetworkPolicyEditor getNetworkPolicyEditor() { return services.mPolicyEditor; @@ -395,13 +444,16 @@ public class BillingCycleSettings extends DataUsageBaseFragment implements public static class CycleEditorFragment extends InstrumentedDialogFragment implements DialogInterface.OnClickListener { private static final String EXTRA_TEMPLATE = "template"; + private static final String EXTRA_CYCLE_TYPE = "cycle_type"; private NumberPicker mCycleDayPicker; + private int mCycleType = CYCLE_TYPE_MONTHLY; public static void show(BillingCycleSettings parent) { if (!parent.isAdded()) return; final Bundle args = new Bundle(); args.putParcelable(EXTRA_TEMPLATE, parent.mNetworkTemplate); + args.putInt(EXTRA_CYCLE_TYPE, parent.getBillingCycleType()); final CycleEditorFragment dialog = new CycleEditorFragment(); dialog.setArguments(args); @@ -419,22 +471,39 @@ public class BillingCycleSettings extends DataUsageBaseFragment implements final Context context = getActivity(); final DataUsageEditController target = (DataUsageEditController) getTargetFragment(); final NetworkPolicyEditor editor = target.getNetworkPolicyEditor(); + final int cycleType = getArguments().getInt(EXTRA_CYCLE_TYPE); 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 TextView textView = (TextView) view.findViewById(R.id.cycle_subtitle); final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); - final int cycleDay = editor.getPolicyCycleDay(template); + int cycleDay = editor.getPolicyCycleDay(template); + int titleRedId = R.string.data_usage_cycle_editor_title; + int minValue = 1; + int maxValue = 31; + if (cycleType == CYCLE_TYPE_WEEKLY) { + cycleDay = editor.getPolicyCycleDayOfWeek(template); + textView.setText(R.string.cycle_type_weekly_sub); + titleRedId = R.string.cycle_type_weekly_title; + maxValue = 7; + } else if (cycleType == CYCLE_TYPE_DAILY) { + cycleDay = editor.getPolicyCycleHour(template); + textView.setText(R.string.cycle_type_daily_sub); + titleRedId = R.string.cycle_type_daily_title; + minValue = 0; + maxValue = 23; + } - mCycleDayPicker.setMinValue(1); - mCycleDayPicker.setMaxValue(31); + mCycleDayPicker.setMinValue(minValue); + mCycleDayPicker.setMaxValue(maxValue); mCycleDayPicker.setValue(cycleDay); mCycleDayPicker.setWrapSelectorWheel(true); - Dialog dialog = builder.setTitle(R.string.data_usage_cycle_editor_title) + Dialog dialog = builder.setTitle(titleRedId) .setView(view) .setPositiveButton(R.string.data_usage_cycle_editor_positive, this) .create(); @@ -447,13 +516,20 @@ public class BillingCycleSettings extends DataUsageBaseFragment implements final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); final DataUsageEditController target = (DataUsageEditController) getTargetFragment(); final NetworkPolicyEditor editor = target.getNetworkPolicyEditor(); + final int cycleType = getArguments().getInt(EXTRA_CYCLE_TYPE); // 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); + if (cycleType == CYCLE_TYPE_WEEKLY) { + editor.setPolicyCycleDayOfWeek(template, cycleDay, cycleTimezone); + } else if (cycleType == CYCLE_TYPE_DAILY) { + editor.setPolicyCycleHour(template, cycleDay, cycleTimezone); + } else { + editor.setPolicyCycleDay(template, cycleDay, cycleTimezone); + } target.updateDataUsage(); } } diff --git a/src/com/android/settings/datausage/DataUsageSummaryPreference.java b/src/com/android/settings/datausage/DataUsageSummaryPreference.java index ff7eb66a1c4..d56aa535900 100644 --- a/src/com/android/settings/datausage/DataUsageSummaryPreference.java +++ b/src/com/android/settings/datausage/DataUsageSummaryPreference.java @@ -54,6 +54,7 @@ import java.util.concurrent.TimeUnit; */ public class DataUsageSummaryPreference extends Preference { private static final long MILLIS_IN_A_DAY = TimeUnit.DAYS.toMillis(1); + private static final long MILLIS_IN_A_HOUR = TimeUnit.HOURS.toMillis(1); private static final long WARNING_AGE = TimeUnit.HOURS.toMillis(6L); @VisibleForTesting static final Typeface SANS_SERIF_MEDIUM = @@ -221,14 +222,24 @@ public class DataUsageSummaryPreference extends Preference { cycleTime.setText(getContext().getString(R.string.billing_cycle_none_left)); } else { int daysLeft = (int) (millisLeft / MILLIS_IN_A_DAY); - MessageFormat msgFormat = new MessageFormat( - getContext().getResources().getString(R.string.billing_cycle_days_left), - Locale.getDefault()); - Map arguments = new HashMap<>(); - arguments.put("count", daysLeft); - cycleTime.setText(daysLeft < 1 - ? getContext().getString(R.string.billing_cycle_less_than_one_day_left) - : msgFormat.format(arguments)); + if (daysLeft >= 1) { + MessageFormat msgFormat = new MessageFormat( + getContext().getResources().getString(R.string.billing_cycle_days_left), + Locale.getDefault()); + Map arguments = new HashMap<>(); + arguments.put("count", daysLeft); + cycleTime.setText(msgFormat.format(arguments)); + } else { + int hoursLeft = (int) (millisLeft / MILLIS_IN_A_HOUR); + MessageFormat msgFormat = new MessageFormat( + getContext().getResources().getString(R.string.billing_cycle_hours_left), + Locale.getDefault()); + Map arguments = new HashMap<>(); + arguments.put("count", hoursLeft); + cycleTime.setText(hoursLeft < 1 + ? getContext().getString(R.string.billing_cycle_less_than_one_hour_left) + : msgFormat.format(arguments)); + } } }