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