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