1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.settings.datausage; 18 19 import android.app.AlertDialog; 20 import android.app.Dialog; 21 import android.content.DialogInterface; 22 import android.os.Bundle; 23 import android.view.LayoutInflater; 24 import android.view.View; 25 import android.widget.EditText; 26 import android.widget.NumberPicker; 27 28 import androidx.annotation.IntegerRes; 29 import androidx.annotation.VisibleForTesting; 30 31 import com.android.car.settings.R; 32 import com.android.car.ui.preference.CarUiDialogFragment; 33 34 /** Dialog that is used to pick the data usage warning/limit threshold bytes. */ 35 public class UsageBytesThresholdPickerDialog extends CarUiDialogFragment { 36 37 /** Tag used to identify dialog in {@link androidx.fragment.app.FragmentManager}. */ 38 public static final String TAG = "UsageBytesThresholdPickerDialog"; 39 private static final String ARG_DIALOG_TITLE_RES = "arg_dialog_title_res"; 40 private static final String ARG_CURRENT_THRESHOLD = "arg_current_threshold"; 41 private static final float MB_GB_SUFFIX_THRESHOLD = 1.5f; 42 43 @VisibleForTesting 44 static final long MIB_IN_BYTES = 1024 * 1024; 45 @VisibleForTesting 46 static final long GIB_IN_BYTES = MIB_IN_BYTES * 1024; 47 @VisibleForTesting 48 static final long MAX_DATA_LIMIT_BYTES = 50000 * GIB_IN_BYTES; 49 50 // Number pickers can be used to pick strings as well. 51 private NumberPicker mBytesUnits; 52 private View mUpArrow; 53 private View mDownArrow; 54 private EditText mThresholdEditor; 55 private BytesThresholdPickedListener mBytesThresholdPickedListener; 56 private long mCurrentThreshold; 57 58 /** 59 * Creates a new instance of the {@link UsageBytesThresholdPickerDialog} with the 60 * {@code currentThreshold} represented with the best units available. 61 */ newInstance(@ntegerRes int dialogTitle, long currentThreshold)62 public static UsageBytesThresholdPickerDialog newInstance(@IntegerRes int dialogTitle, 63 long currentThreshold) { 64 UsageBytesThresholdPickerDialog dialog = new UsageBytesThresholdPickerDialog(); 65 Bundle args = new Bundle(); 66 args.putInt(ARG_DIALOG_TITLE_RES, dialogTitle); 67 args.putLong(ARG_CURRENT_THRESHOLD, currentThreshold); 68 dialog.setArguments(args); 69 return dialog; 70 } 71 72 /** Sets a {@link BytesThresholdPickedListener}. */ setBytesThresholdPickedListener( BytesThresholdPickedListener bytesThresholdPickedListener)73 public void setBytesThresholdPickedListener( 74 BytesThresholdPickedListener bytesThresholdPickedListener) { 75 mBytesThresholdPickedListener = bytesThresholdPickedListener; 76 } 77 78 @Override onCreateDialog(Bundle savedInstanceState)79 public Dialog onCreateDialog(Bundle savedInstanceState) { 80 AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); 81 82 // Use builder context to keep consistent theme. 83 LayoutInflater inflater = LayoutInflater.from(builder.getContext()); 84 View view = inflater.inflate(R.layout.usage_bytes_threshold_picker, 85 /* root= */ null, /* attachToRoot= */ false); 86 87 mCurrentThreshold = getArguments().getLong(ARG_CURRENT_THRESHOLD); 88 if (mCurrentThreshold < 0) { 89 mCurrentThreshold = 0; 90 } 91 92 String[] units = getContext().getResources().getStringArray(R.array.bytes_picker_sizes); 93 mBytesUnits = view.findViewById(R.id.bytes_units); 94 mBytesUnits.setMinValue(0); 95 mBytesUnits.setMaxValue(units.length - 1); 96 mBytesUnits.setDisplayedValues(units); 97 98 mThresholdEditor = view.findViewById(R.id.bytes_threshold); 99 100 mUpArrow = view.findViewById(R.id.up_arrow_container); 101 mUpArrow.setOnClickListener(v -> mBytesUnits.setValue(mBytesUnits.getValue() - 1)); 102 103 mDownArrow = view.findViewById(R.id.down_arrow_container); 104 mDownArrow.setOnClickListener(v -> mBytesUnits.setValue(mBytesUnits.getValue() + 1)); 105 106 updateCurrentView(mCurrentThreshold); 107 108 return builder 109 .setTitle(getArguments().getInt(ARG_DIALOG_TITLE_RES)) 110 .setView(view) 111 .setPositiveButton(R.string.usage_bytes_threshold_picker_positive_button, 112 /* onClickListener= */ this) 113 .create(); 114 } 115 116 @Override onDialogClosed(boolean positiveResult)117 protected void onDialogClosed(boolean positiveResult) { 118 } 119 120 @Override onClick(DialogInterface dialog, int which)121 public void onClick(DialogInterface dialog, int which) { 122 if (which == DialogInterface.BUTTON_POSITIVE) { 123 long newThreshold = getCurrentThreshold(); 124 if (mBytesThresholdPickedListener != null 125 && mCurrentThreshold != newThreshold) { 126 mBytesThresholdPickedListener.onThresholdPicked(newThreshold); 127 } 128 } 129 } 130 131 /** Gets the threshold currently represented by this {@link UsageBytesThresholdPickerDialog}. */ getCurrentThreshold()132 public long getCurrentThreshold() { 133 String bytesString = mThresholdEditor.getText().toString(); 134 if (bytesString.isEmpty() || bytesString.equals(".")) { 135 bytesString = "0"; 136 } 137 138 long bytes = (long) (Float.valueOf(bytesString) * (mBytesUnits.getValue() == 0 139 ? MIB_IN_BYTES : GIB_IN_BYTES)); 140 141 // To fix the overflow problem. 142 long correctedBytes = Math.min(MAX_DATA_LIMIT_BYTES, bytes); 143 144 return correctedBytes; 145 } 146 147 @VisibleForTesting setThresholdEditor(long threshold)148 void setThresholdEditor(long threshold) { 149 updateCurrentView(threshold); 150 } 151 updateCurrentView(long threshold)152 private void updateCurrentView(long threshold) { 153 String bytesText; 154 if (threshold > MB_GB_SUFFIX_THRESHOLD * GIB_IN_BYTES) { 155 bytesText = formatText(threshold / (float) GIB_IN_BYTES); 156 mBytesUnits.setValue(1); 157 } else { 158 bytesText = formatText(threshold / (float) MIB_IN_BYTES); 159 mBytesUnits.setValue(0); 160 } 161 mThresholdEditor.setText(bytesText); 162 mThresholdEditor.setSelection(0, bytesText.length()); 163 } 164 formatText(float v)165 private String formatText(float v) { 166 v = Math.round(v * 100) / 100f; 167 return String.valueOf(v); 168 } 169 170 /** A listener that is called when a date is selected. */ 171 public interface BytesThresholdPickedListener { 172 /** A method that determines how to process the selected day of month. */ onThresholdPicked(long numBytes)173 void onThresholdPicked(long numBytes); 174 } 175 } 176