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.icu.text.MeasureFormat; 23 import android.icu.util.MeasureUnit; 24 import android.os.Bundle; 25 import android.view.LayoutInflater; 26 import android.view.View; 27 import android.widget.EditText; 28 import android.widget.NumberPicker; 29 30 import androidx.annotation.IntegerRes; 31 import androidx.annotation.VisibleForTesting; 32 33 import com.android.car.settings.R; 34 import com.android.car.ui.preference.CarUiDialogFragment; 35 36 /** Dialog that is used to pick the data usage warning/limit threshold bytes. */ 37 public class UsageBytesThresholdPickerDialog extends CarUiDialogFragment { 38 39 /** Tag used to identify dialog in {@link androidx.fragment.app.FragmentManager}. */ 40 public static final String TAG = "UsageBytesThresholdPickerDialog"; 41 private static final String ARG_DIALOG_TITLE_RES = "arg_dialog_title_res"; 42 private static final String ARG_CURRENT_THRESHOLD = "arg_current_threshold"; 43 private static final float MB_GB_SUFFIX_THRESHOLD = 1.5f; 44 45 @VisibleForTesting 46 static final long MIB_IN_BYTES = 1024 * 1024; 47 @VisibleForTesting 48 static final long GIB_IN_BYTES = MIB_IN_BYTES * 1024; 49 @VisibleForTesting 50 static final long MAX_DATA_LIMIT_BYTES = 50000 * GIB_IN_BYTES; 51 52 // Number pickers can be used to pick strings as well. 53 private NumberPicker mBytesUnits; 54 private View mUpArrow; 55 private View mDownArrow; 56 private EditText mThresholdEditor; 57 private BytesThresholdPickedListener mBytesThresholdPickedListener; 58 private long mCurrentThreshold; 59 60 /** 61 * Creates a new instance of the {@link UsageBytesThresholdPickerDialog} with the 62 * {@code currentThreshold} represented with the best units available. 63 */ newInstance(@ntegerRes int dialogTitle, long currentThreshold)64 public static UsageBytesThresholdPickerDialog newInstance(@IntegerRes int dialogTitle, 65 long currentThreshold) { 66 UsageBytesThresholdPickerDialog dialog = new UsageBytesThresholdPickerDialog(); 67 Bundle args = new Bundle(); 68 args.putInt(ARG_DIALOG_TITLE_RES, dialogTitle); 69 args.putLong(ARG_CURRENT_THRESHOLD, currentThreshold); 70 dialog.setArguments(args); 71 return dialog; 72 } 73 74 /** Sets a {@link BytesThresholdPickedListener}. */ setBytesThresholdPickedListener( BytesThresholdPickedListener bytesThresholdPickedListener)75 public void setBytesThresholdPickedListener( 76 BytesThresholdPickedListener bytesThresholdPickedListener) { 77 mBytesThresholdPickedListener = bytesThresholdPickedListener; 78 } 79 80 @Override onCreateDialog(Bundle savedInstanceState)81 public Dialog onCreateDialog(Bundle savedInstanceState) { 82 AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); 83 84 // Use builder context to keep consistent theme. 85 LayoutInflater inflater = LayoutInflater.from(builder.getContext()); 86 View view = inflater.inflate(R.layout.usage_bytes_threshold_picker, 87 /* root= */ null, /* attachToRoot= */ false); 88 89 mCurrentThreshold = getArguments().getLong(ARG_CURRENT_THRESHOLD); 90 if (mCurrentThreshold < 0) { 91 mCurrentThreshold = 0; 92 } 93 94 MeasureFormat formatter = MeasureFormat.getInstance( 95 getContext().getResources().getConfiguration().locale, 96 MeasureFormat.FormatWidth.SHORT); 97 String[] units = new String[] { 98 formatter.getUnitDisplayName(MeasureUnit.MEGABYTE), 99 formatter.getUnitDisplayName(MeasureUnit.GIGABYTE) 100 }; 101 mBytesUnits = view.findViewById(R.id.bytes_units); 102 mBytesUnits.setMinValue(0); 103 mBytesUnits.setMaxValue(units.length - 1); 104 mBytesUnits.setDisplayedValues(units); 105 106 mThresholdEditor = view.findViewById(R.id.bytes_threshold); 107 108 mUpArrow = view.findViewById(R.id.up_arrow_container); 109 mUpArrow.setOnClickListener(v -> mBytesUnits.setValue(mBytesUnits.getValue() - 1)); 110 111 mDownArrow = view.findViewById(R.id.down_arrow_container); 112 mDownArrow.setOnClickListener(v -> mBytesUnits.setValue(mBytesUnits.getValue() + 1)); 113 114 updateCurrentView(mCurrentThreshold); 115 116 return builder 117 .setTitle(getArguments().getInt(ARG_DIALOG_TITLE_RES)) 118 .setView(view) 119 .setPositiveButton(R.string.usage_bytes_threshold_picker_positive_button, 120 /* onClickListener= */ this) 121 .create(); 122 } 123 124 @Override onDialogClosed(boolean positiveResult)125 protected void onDialogClosed(boolean positiveResult) { 126 } 127 128 @Override onClick(DialogInterface dialog, int which)129 public void onClick(DialogInterface dialog, int which) { 130 if (which == DialogInterface.BUTTON_POSITIVE) { 131 long newThreshold = getCurrentThreshold(); 132 if (mBytesThresholdPickedListener != null 133 && mCurrentThreshold != newThreshold) { 134 mBytesThresholdPickedListener.onThresholdPicked(newThreshold); 135 } 136 } 137 } 138 139 /** Gets the threshold currently represented by this {@link UsageBytesThresholdPickerDialog}. */ getCurrentThreshold()140 public long getCurrentThreshold() { 141 String bytesString = mThresholdEditor.getText().toString(); 142 if (bytesString.isEmpty() || bytesString.equals(".")) { 143 bytesString = "0"; 144 } 145 146 long bytes = (long) (Float.valueOf(bytesString) * (mBytesUnits.getValue() == 0 147 ? MIB_IN_BYTES : GIB_IN_BYTES)); 148 149 // To fix the overflow problem. 150 long correctedBytes = Math.min(MAX_DATA_LIMIT_BYTES, bytes); 151 152 return correctedBytes; 153 } 154 155 @VisibleForTesting setThresholdEditor(long threshold)156 void setThresholdEditor(long threshold) { 157 updateCurrentView(threshold); 158 } 159 updateCurrentView(long threshold)160 private void updateCurrentView(long threshold) { 161 String bytesText; 162 if (threshold > MB_GB_SUFFIX_THRESHOLD * GIB_IN_BYTES) { 163 bytesText = formatText(threshold / (float) GIB_IN_BYTES); 164 mBytesUnits.setValue(1); 165 } else { 166 bytesText = formatText(threshold / (float) MIB_IN_BYTES); 167 mBytesUnits.setValue(0); 168 } 169 mThresholdEditor.setText(bytesText); 170 mThresholdEditor.setSelection(0, bytesText.length()); 171 } 172 formatText(float v)173 private String formatText(float v) { 174 v = Math.round(v * 100) / 100f; 175 return String.valueOf(v); 176 } 177 178 /** A listener that is called when a date is selected. */ 179 public interface BytesThresholdPickedListener { 180 /** A method that determines how to process the selected day of month. */ onThresholdPicked(long numBytes)181 void onThresholdPicked(long numBytes); 182 } 183 } 184