Settings: Add support for daily and weekly data usage cycles [2/2]

Change-Id: Ib43ca55f0f7fec5202982f5786176184809ab9f8
This commit is contained in:
Ido Ben-Hur
2025-08-22 14:30:54 +03:00
committed by Joey
parent dcaec74227
commit 0103dce92f
6 changed files with 136 additions and 13 deletions

View File

@@ -21,6 +21,7 @@
android:layout_height="wrap_content">
<TextView
android:id="@+id/cycle_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"

View File

@@ -169,4 +169,17 @@
<item>2</item>
<item>3</item>
</string-array>
<!-- Data Usage Cycle -->
<string-array name="billing_cycle_type_entries" translatable="false">
<item>@string/billing_cycle_type_monthly</item>
<item>@string/billing_cycle_type_weekly</item>
<item>@string/billing_cycle_type_daily</item>
</string-array>
<string-array name="billing_cycle_type_values" translatable="false">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
</resources>

View File

@@ -369,4 +369,19 @@
<!-- Force dark mode -->
<string name="force_dark_title">Dark mode for third-party apps</string>
<string name="force_dark_summary">Forces darker colors in apps lacking dark mode. Some screens may appear off.</string>
<!-- Data Usage Cycle -->
<string name="billing_cycle_type_title">Mobile data usage cycle type</string>
<string name="billing_cycle_type_monthly">Monthly</string>
<string name="billing_cycle_type_weekly">Weekly</string>
<string name="billing_cycle_type_daily">Daily</string>
<string name="billing_cycle_less_than_one_hour_left">Less than 1 hour left</string>
<string name="billing_cycle_hours_left">{count, plural,
=1 {# hour left}
other {# hours left}
}</string>
<string name="cycle_type_weekly_title">Usage cycle reset day</string>
<string name="cycle_type_weekly_sub">Day of each week:\n1 = Monday</string>
<string name="cycle_type_daily_title">Usage cycle reset hour</string>
<string name="cycle_type_daily_sub">Hour of each day:</string>
</resources>

View File

@@ -19,6 +19,13 @@
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/billing_cycle">
<ListPreference
android:key="billing_cycle_type"
android:title="@string/billing_cycle_type_title"
android:summary="%s"
android:entries="@array/billing_cycle_type_entries"
android:entryValues="@array/billing_cycle_type_values" />
<Preference
android:key="billing_cycle"
android:title="@string/app_usage_cycle" />

View File

@@ -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();
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();
}
}

View File

@@ -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,16 +222,26 @@ public class DataUsageSummaryPreference extends Preference {
cycleTime.setText(getContext().getString(R.string.billing_cycle_none_left));
} else {
int daysLeft = (int) (millisLeft / MILLIS_IN_A_DAY);
if (daysLeft >= 1) {
MessageFormat msgFormat = new MessageFormat(
getContext().getResources().getString(R.string.billing_cycle_days_left),
Locale.getDefault());
Map<String, Object> arguments = new HashMap<>();
arguments.put("count", daysLeft);
cycleTime.setText(daysLeft < 1
? getContext().getString(R.string.billing_cycle_less_than_one_day_left)
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<String, Object> 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));
}
}
}
private void updateCarrierInfo(TextView carrierInfo) {