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.os.Handler;
24 import android.view.LayoutInflater;
25 import android.view.MotionEvent;
26 import android.view.View;
27 import android.widget.NumberPicker;
28 
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 start day of month to track a data usage cycle. */
35 public class UsageCycleResetDayOfMonthPickerDialog extends CarUiDialogFragment {
36 
37     private static final String ARG_SELECTED_DAY_OF_MONTH = "arg_selected_day_of_month";
38 
39     /**
40      * Defines the time frequency at which touch listener should be triggered when holding either
41      * arrow button.
42      */
43     @VisibleForTesting
44     static final int TIME_INTERVAL_MILLIS = 250;
45 
46     private static final int MIN_DAY = 1;
47     private static final int MAX_DAY = 31;
48     private ResetDayOfMonthPickedListener mResetDayOfMonthPickedListener;
49     private NumberPicker mCycleDayOfMonthPicker;
50     private View mUpArrow;
51     private View mDownArrow;
52 
53     /**
54      * Creates a new instance of the {@link UsageCycleResetDayOfMonthPickerDialog} with the {@link
55      * NumberPicker} set to showing the value {@code startDayOfMonth}.
56      */
newInstance(int startDayOfMonth)57     public static UsageCycleResetDayOfMonthPickerDialog newInstance(int startDayOfMonth) {
58         UsageCycleResetDayOfMonthPickerDialog dialog = new UsageCycleResetDayOfMonthPickerDialog();
59         Bundle args = new Bundle();
60         args.putInt(ARG_SELECTED_DAY_OF_MONTH, startDayOfMonth);
61         dialog.setArguments(args);
62         return dialog;
63     }
64 
65     /** Sets a {@link ResetDayOfMonthPickedListener}. */
setResetDayOfMonthPickedListener( ResetDayOfMonthPickedListener resetDayOfMonthPickedListener)66     public void setResetDayOfMonthPickedListener(
67             ResetDayOfMonthPickedListener resetDayOfMonthPickedListener) {
68         mResetDayOfMonthPickedListener = resetDayOfMonthPickedListener;
69     }
70 
71     @Override
onCreateDialog(Bundle savedInstanceState)72     public Dialog onCreateDialog(Bundle savedInstanceState) {
73         AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
74 
75         // Use builder context to keep consistent theme.
76         LayoutInflater inflater = LayoutInflater.from(builder.getContext());
77         View view = inflater.inflate(R.layout.usage_cycle_reset_day_of_month_picker,
78                 /* root= */ null, /* attachToRoot= */ false);
79 
80         int cycleDayOfMonth = getArguments().getInt(ARG_SELECTED_DAY_OF_MONTH);
81         if (cycleDayOfMonth < MIN_DAY) {
82             cycleDayOfMonth = MIN_DAY;
83         }
84         if (cycleDayOfMonth > MAX_DAY) {
85             cycleDayOfMonth = MAX_DAY;
86         }
87 
88         mCycleDayOfMonthPicker = view.findViewById(R.id.cycle_reset_day_of_month);
89         mCycleDayOfMonthPicker.setMinValue(MIN_DAY);
90         mCycleDayOfMonthPicker.setMaxValue(MAX_DAY);
91         mCycleDayOfMonthPicker.setValue(cycleDayOfMonth);
92         mCycleDayOfMonthPicker.setWrapSelectorWheel(true);
93 
94         mUpArrow = view.findViewById(R.id.up_arrow_container);
95         mUpArrow.setOnTouchListener(new CycleArrowTouchListener(
96                 () -> mCycleDayOfMonthPicker.setValue(mCycleDayOfMonthPicker.getValue() - 1),
97                 TIME_INTERVAL_MILLIS));
98 
99         mDownArrow = view.findViewById(R.id.down_arrow_container);
100         mDownArrow.setOnTouchListener(new CycleArrowTouchListener(
101                 () -> mCycleDayOfMonthPicker.setValue(mCycleDayOfMonthPicker.getValue() + 1),
102                 TIME_INTERVAL_MILLIS));
103 
104         return builder
105                 .setTitle(R.string.cycle_reset_day_of_month_picker_title)
106                 .setView(view)
107                 .setPositiveButton(R.string.cycle_reset_day_of_month_picker_positive_button,
108                         (dialog, which) -> {
109                             if (which == DialogInterface.BUTTON_POSITIVE) {
110                                 if (mResetDayOfMonthPickedListener != null) {
111                                     mResetDayOfMonthPickedListener.onDayOfMonthPicked(
112                                             mCycleDayOfMonthPicker.getValue());
113                                 }
114                             }
115                         })
116                 .create();
117     }
118 
119     @Override
120     protected void onDialogClosed(boolean positiveResult) {
121     }
122 
123     /** Gets the current day of month selected by the {@link NumberPicker}. */
124     public int getSelectedDayOfMonth() {
125         return mCycleDayOfMonthPicker.getValue();
126     }
127 
128     /** A listener that is called when a date is selected. */
129     public interface ResetDayOfMonthPickedListener {
130         /** A method that determines how to process the selected day of month. */
131         void onDayOfMonthPicked(int dayOfMonth);
132     }
133 
134     private static class CycleArrowTouchListener implements View.OnTouchListener {
135 
136         private final IntervalActionListener mIntervalActionListener;
137         private final long mTimeIntervalMillis;
138 
139         private Handler mHandler = new Handler();
140         private Runnable mAction;
141 
142         CycleArrowTouchListener(IntervalActionListener listener, long timeIntervalMillis) {
143             mIntervalActionListener = listener;
144             mTimeIntervalMillis = timeIntervalMillis;
145 
146             mAction = () -> {
147                 mHandler.postDelayed(this.mAction, mTimeIntervalMillis);
148                 maybeTriggerAction();
149             };
150         }
151 
152         @Override
153         public boolean onTouch(View v, MotionEvent event) {
154             switch (event.getAction()) {
155                 case MotionEvent.ACTION_DOWN:
156                     mHandler.removeCallbacks(mAction);
157                     mHandler.postDelayed(mAction, mTimeIntervalMillis);
158                     maybeTriggerAction();
159                     v.setPressed(true);
160                     return true;
161                 case MotionEvent.ACTION_UP:
162                 case MotionEvent.ACTION_CANCEL:
163                     mHandler.removeCallbacks(mAction);
164                     v.setPressed(false);
165                     return true;
166             }
167             return false;
168         }
169 
170         private void maybeTriggerAction() {
171             if (mIntervalActionListener != null) {
172                 mIntervalActionListener.takeAction();
173             }
174         }
175 
176         /** Action that should be taken per time interval that the button is held. */
177         interface IntervalActionListener {
178             /** Defines the action to take at each time interval. */
179             void takeAction();
180         }
181     }
182 }
183