/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.car.settings.datausage; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; import android.os.Handler; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.widget.NumberPicker; import androidx.annotation.VisibleForTesting; import com.android.car.settings.R; import com.android.car.ui.preference.CarUiDialogFragment; /** Dialog that is used to pick the start day of month to track a data usage cycle. */ public class UsageCycleResetDayOfMonthPickerDialog extends CarUiDialogFragment { private static final String ARG_SELECTED_DAY_OF_MONTH = "arg_selected_day_of_month"; /** * Defines the time frequency at which touch listener should be triggered when holding either * arrow button. */ @VisibleForTesting static final int TIME_INTERVAL_MILLIS = 250; private static final int MIN_DAY = 1; private static final int MAX_DAY = 31; private ResetDayOfMonthPickedListener mResetDayOfMonthPickedListener; private NumberPicker mCycleDayOfMonthPicker; private View mUpArrow; private View mDownArrow; /** * Creates a new instance of the {@link UsageCycleResetDayOfMonthPickerDialog} with the {@link * NumberPicker} set to showing the value {@code startDayOfMonth}. */ public static UsageCycleResetDayOfMonthPickerDialog newInstance(int startDayOfMonth) { UsageCycleResetDayOfMonthPickerDialog dialog = new UsageCycleResetDayOfMonthPickerDialog(); Bundle args = new Bundle(); args.putInt(ARG_SELECTED_DAY_OF_MONTH, startDayOfMonth); dialog.setArguments(args); return dialog; } /** Sets a {@link ResetDayOfMonthPickedListener}. */ public void setResetDayOfMonthPickedListener( ResetDayOfMonthPickedListener resetDayOfMonthPickedListener) { mResetDayOfMonthPickedListener = resetDayOfMonthPickedListener; } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); // Use builder context to keep consistent theme. LayoutInflater inflater = LayoutInflater.from(builder.getContext()); View view = inflater.inflate(R.layout.usage_cycle_reset_day_of_month_picker, /* root= */ null, /* attachToRoot= */ false); int cycleDayOfMonth = getArguments().getInt(ARG_SELECTED_DAY_OF_MONTH); if (cycleDayOfMonth < MIN_DAY) { cycleDayOfMonth = MIN_DAY; } if (cycleDayOfMonth > MAX_DAY) { cycleDayOfMonth = MAX_DAY; } mCycleDayOfMonthPicker = view.findViewById(R.id.cycle_reset_day_of_month); mCycleDayOfMonthPicker.setMinValue(MIN_DAY); mCycleDayOfMonthPicker.setMaxValue(MAX_DAY); mCycleDayOfMonthPicker.setValue(cycleDayOfMonth); mCycleDayOfMonthPicker.setWrapSelectorWheel(true); mUpArrow = view.findViewById(R.id.up_arrow_container); mUpArrow.setOnTouchListener(new CycleArrowTouchListener( () -> mCycleDayOfMonthPicker.setValue(mCycleDayOfMonthPicker.getValue() - 1), TIME_INTERVAL_MILLIS)); mDownArrow = view.findViewById(R.id.down_arrow_container); mDownArrow.setOnTouchListener(new CycleArrowTouchListener( () -> mCycleDayOfMonthPicker.setValue(mCycleDayOfMonthPicker.getValue() + 1), TIME_INTERVAL_MILLIS)); return builder .setTitle(R.string.cycle_reset_day_of_month_picker_title) .setView(view) .setPositiveButton(R.string.cycle_reset_day_of_month_picker_positive_button, (dialog, which) -> { if (which == DialogInterface.BUTTON_POSITIVE) { if (mResetDayOfMonthPickedListener != null) { mResetDayOfMonthPickedListener.onDayOfMonthPicked( mCycleDayOfMonthPicker.getValue()); } } }) .create(); } @Override protected void onDialogClosed(boolean positiveResult) { } /** Gets the current day of month selected by the {@link NumberPicker}. */ public int getSelectedDayOfMonth() { return mCycleDayOfMonthPicker.getValue(); } /** A listener that is called when a date is selected. */ public interface ResetDayOfMonthPickedListener { /** A method that determines how to process the selected day of month. */ void onDayOfMonthPicked(int dayOfMonth); } private static class CycleArrowTouchListener implements View.OnTouchListener { private final IntervalActionListener mIntervalActionListener; private final long mTimeIntervalMillis; private Handler mHandler = new Handler(); private Runnable mAction; CycleArrowTouchListener(IntervalActionListener listener, long timeIntervalMillis) { mIntervalActionListener = listener; mTimeIntervalMillis = timeIntervalMillis; mAction = () -> { mHandler.postDelayed(this.mAction, mTimeIntervalMillis); maybeTriggerAction(); }; } @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mHandler.removeCallbacks(mAction); mHandler.postDelayed(mAction, mTimeIntervalMillis); maybeTriggerAction(); v.setPressed(true); return true; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mHandler.removeCallbacks(mAction); v.setPressed(false); return true; } return false; } private void maybeTriggerAction() { if (mIntervalActionListener != null) { mIntervalActionListener.takeAction(); } } /** Action that should be taken per time interval that the button is held. */ interface IntervalActionListener { /** Defines the action to take at each time interval. */ void takeAction(); } } }