Keep the chronometer always running without a service - java

I'm using a chronometer and it needs to run persistently until stopped.
The code I'm using below works well and the chronometer keeps running, however, when the device restarts, I get negative values.
SharedPreferences sharedPreferences = getSharedPreferences("chronometer", MODE_PRIVATE);
//Check if the start time was saved before
long elapsedTime = sharedPreferences.getLong("elapsed_time", 0);
if (elapsedTime != 0) {
//It means the chronometer was running before, so we set the time from SharedPreferences
chronometer.setBase(elapsedTime);
chronometer.start();
}
//Code below starts the chronometer, after the user manually starts it
Button startButton = findViewById(R.id.btnStart);
startButton.setOnClickListener(v -> {
//Save the starting time
long elapsedRealtime = SystemClock.elapsedRealtime();
sharedPreferences.edit().putLong("elapsed_time", elapsedRealtime).apply();
chronometer.setBase(SystemClock.elapsedRealtime());
chronometer.start();
});
Why does the chronometer display negative values after a re-boot & how can I sort it out?

From Android Documentation
Chronometer
Class that implements a simple timer.
You can give it a start time in the SystemClock#elapsedRealtime
timebase, and it counts up from that.
The timebase of this class is based on SystemClock.elapsedRealtime().
elapsedRealtime
public static long elapsedRealtime()
Returns milliseconds since boot, including time spent in sleep.
Back to your questions:
Why does the chronometer display negative values after a re-boot?
Here is an example to demonstrate this scenario.
Assume your phone is running about 10 minutes from last booting, at that time users open the activity, the elapsedRealtime() method will return 600000L (10 mins = 10 * 60 * 1000 milliseconds). When users click on the Start button, you set that time for Chronometer as basetime and save 600000L to SharePreferences using elapsed_time key.
Right now: elapsedRealTime() = 10 mins | Chronometer = 00:00
After 1 min: elapsedRealtime() = 11 mins | Chronometer = 01:00
After 10 minutes: elapsedRealTime() = 20 mins | Chronometer = 10:00
At this time users press the BACK key to exit the app and restart the phone.
The phone is kinda slow, so it took 2 mins from booting. Users open the activity again. At this time.
elapsedRealtime() returns 120000L (2 mins = 2 * 60 * 1000 milliseconds)
The value you saved in the SharePreferences (elapsed_time) is 600000L
Because the value you set as basetime of Chronometer is ahead of the elapsedRealtime(), so the Chronometer will display -08:00 and start counting up from that value.
How can I sort it out?
You can save the elapsed time of Chronometer and the elapsed time from the last time users leave the activity until the next time users open the activity. Then using these two values to calculate the basetime for Chronometer.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<Chronometer
android:id="#+id/chronometer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:textColor="#F00"
android:textSize="24sp" />
<Button
android:id="#+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start" />
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
private static final String KEY_CHRONOMETER_ELAPSED_TIME = "chronometerElapsedTime";
private static final String KEY_CHRONOMETER_STOPPED_TIME = "chronometerStoppedTime";
private Chronometer chronometer;
private SharedPreferences prefs;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
chronometer = findViewById(R.id.chronometer);
// Code below starts the chronometer, after the user manually starts it
Button startButton = findViewById(R.id.btnStart);
startButton.setOnClickListener(v -> {
setElapsedTime(-1);
setStoppedTime(-1);
chronometer.setBase(SystemClock.elapsedRealtime());
chronometer.start();
});
}
#Override
protected void onStart() {
super.onStart();
prefs = getSharedPreferences("chronometer", MODE_PRIVATE);
if (prefs.contains(KEY_CHRONOMETER_ELAPSED_TIME)
&& prefs.contains(KEY_CHRONOMETER_STOPPED_TIME)) {
long chronometerElapsedTime = prefs.getLong(KEY_CHRONOMETER_ELAPSED_TIME, -1);
long chronometerStoppedTime = prefs.getLong(KEY_CHRONOMETER_STOPPED_TIME, -1);
if (chronometerElapsedTime != -1 && chronometerStoppedTime != -1) {
long now = System.currentTimeMillis();
long elapsedTimeFromLastStop = now - chronometerStoppedTime; // Including restart time
long elapsedRealTime = SystemClock.elapsedRealtime();
long base = elapsedRealTime - (chronometerElapsedTime + elapsedTimeFromLastStop);
chronometer.setBase(base);
chronometer.start();
}
}
}
#Override
protected void onStop() {
setElapsedTime(getChronometerTimeMs());
setStoppedTime(System.currentTimeMillis());
super.onStop();
}
private void setElapsedTime(long elapsedTimeMs) {
prefs.edit().putLong(KEY_CHRONOMETER_ELAPSED_TIME, elapsedTimeMs).apply();
}
private void setStoppedTime(long stoppedTimeMs) {
prefs.edit().putLong(KEY_CHRONOMETER_STOPPED_TIME, stoppedTimeMs).apply();
}
private long getChronometerTimeMs() {
long chronometerTimeMs = 0;
// Regex for HH:MM:SS or MM:SS
String regex = "([0-1]?\\d|2[0-3])(?::([0-5]?\\d))?(?::([0-5]?\\d))?";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(chronometer.getText());
if (matcher.find()) {
boolean isHHMMSSFormat = matcher.groupCount() == 4;
if (isHHMMSSFormat) {
int hour = Integer.valueOf(matcher.group(1));
int minute = Integer.valueOf(matcher.group(2));
int second = Integer.valueOf(matcher.group(3));
chronometerTimeMs = (hour * DateUtils.HOUR_IN_MILLIS)
+ (minute * DateUtils.MINUTE_IN_MILLIS)
+ (second * DateUtils.SECOND_IN_MILLIS);
} else {
int minute = Integer.valueOf(matcher.group(1));
int second = Integer.valueOf(matcher.group(2));
chronometerTimeMs = (minute * DateUtils.MINUTE_IN_MILLIS)
+ (second * DateUtils.SECOND_IN_MILLIS);
}
}
return chronometerTimeMs;
}
}

You should save the elapsedMillis into shared preferences and when the device restarts and you set the base of the chronometer you should do the followgin
chronometer.setBase(SystemClock.elapsedRealtime() - millis)
where millis is the value you have in the preferences

Related

Keep running Multiple countdown timers and multiple stopwatches in background using Android

I am new to Android and Java and I have been trying to solve an issue on timers.Please help
I have an App where the user can create multiple countdown timers and multiple stopwatches.
Example code of countdown timer is given below .(Stop watch is also implemented in a similar fashion using handler). I want the timers and stopwatches to run in the background even when the user leaves the app.And the current state of timers should be shown when the user comes back to the app again,rather than showing the initial state.
The problem is my timers and stopwatches are running when I leave the app,but the results are not published into the UI when the user comes back to the app. A new instance is created and the fresh timers with 00:00:00 are shown again,while the old timers keep running in the background and goes on without being able to stop.Also in the following code I have a listener which publishes the result into the UI in its callback.This interface is implemented in my activity. So can anyone please suggest a solution for this ?
public class CountDownTimerService {
public interface CounterTimeChangeListener {
public void onCounterTimeChange(String time,Boolean counterFinished);
}
CounterTimeChangeListener listener;
MyCountDownTimer myCountDownTimer;
String output;
int length;
long startTime = 0;
long currentTime = 0;
long timeElapsed = 0;
long timeRemaining = 0;
long prevTimeRemaining = 0;
public CountDownTimerService(int countDownFutureTime)
{
myCountDownTimer= new MyCountDownTimer(countDownFutureTime,1000);
length=countDownFutureTime;
}
// code for countdown timer
// this function format the time to hours seconds and minutes format
public String formatTime(long mills)
{
output = "";
long seconds = mills / 1000;
long minutes = seconds / 60;
long hours = minutes / 60;
seconds = seconds % 60;
minutes = minutes % 60;
hours = hours % 60;
String secondsD = String.valueOf(seconds);
String minutesD = String.valueOf(minutes);
String hoursD = String.valueOf(hours);
if (seconds < 10)
secondsD = "0" + seconds;
if (minutes < 10)
minutesD = "0" + minutes;
if (hours < 10)
hoursD = "0" + hours;
output = hoursD +":" +minutesD +":"+ secondsD;
return output;
}
// this function is called on start button click
public void CountDownControl(int state) {
if(state==0)
{
startTime = System.currentTimeMillis();
myCountDownTimer.start();
}
else if(state==1)
{
// when the pause is clicked
myCountDownTimer.cancel();
currentTime = System.currentTimeMillis();
timeElapsed = currentTime - startTime;
if (prevTimeRemaining == 0)
timeRemaining = length - timeElapsed;
else
timeRemaining = prevTimeRemaining - timeElapsed;
prevTimeRemaining = timeRemaining;
myCountDownTimer= new MyCountDownTimer(timeRemaining,1000);
if (listener!=null) {
listener.onCounterTimeChange(formatTime(timeRemaining), false);
}
}
}
// this function is called on reset button click
public void resetInitial()
{
prevTimeRemaining = 0;
myCountDownTimer.cancel();
if (listener!=null) {
listener.onCounterTimeChange(formatTime(length),false);
}
myCountDownTimer= new MyCountDownTimer(length,1000);
}
public class MyCountDownTimer extends CountDownTimer {
public MyCountDownTimer(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
public void onTick (long millisUntilFinished) {
// calls the fragment's callback function passing the time value
Log.e("in counter","1");
if (listener!=null) {
Log.e("in counter","2");
listener.onCounterTimeChange(formatTime(millisUntilFinished), false);
}
}
public void onFinish() {
/*
* write code for an alarm when timer times out
*
* */
prevTimeRemaining = 0;
if (listener!=null) {
listener.onCounterTimeChange(formatTime(length),true);
}
myCountDownTimer= new MyCountDownTimer(length,1000);
}
}
public void setCounterTimeChangeListener(CounterTimeChangeListener listener) {
this.listener = listener;
}
}

Trying to make a timer loop

I'm trying to make a count down time loop.
I want it to make it loop till "int x" reaches 100.
I've tried adding a do/while and a for loop but I think I'm doing it wrong.
Any suggestions?
Thanks for looking. :-) `
public class MainActivity extends Activity{
Button buttonStartTime;
// clicking this button will start time count down
TextView textViewShowTime; // will show the time
TextView shots;
CountDownTimer countDownTimer; // built in android class CountDownTimer
long totalTimeCountInMilliseconds; // total count down time in milliseconds
long timeBlinkInMilliseconds; // start time of start blinking
boolean blink; // controls the blinking .. on and off
static int x = 3;
int whole = 100;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getReferenceOfViews (); // get all views
setActionListeners (); // set action listerns
totalTimeCountInMilliseconds = 60 * 1000; // time count for 3 minutes = 180 seconds
timeBlinkInMilliseconds = 10 * 1000; // blink starts at 1 minutes = 60 seconds
}
private void setActionListeners() {
buttonStartTime.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
textViewShowTime.setTextAppearance(getApplicationContext(), R.style.normalText);
countDownTimer = new CountDownTimer(totalTimeCountInMilliseconds, 500) {
// 500 means, onTick function will be called at every 500 milliseconds
#Override
public void onTick(long leftTimeInMilliseconds) {
long seconds = leftTimeInMilliseconds / 1000;
if ( leftTimeInMilliseconds < timeBlinkInMilliseconds ) {
textViewShowTime.setTextAppearance(getApplicationContext(), R.style.blinkText);
// change the style of the textview .. giving a red alert style
if ( blink ) {
textViewShowTime.setVisibility(View.VISIBLE);
// if blink is true, textview will be visible
} else {
textViewShowTime.setVisibility(View.INVISIBLE);
}
blink = !blink; // toggle the value of blink
}
textViewShowTime.setText(String.format("%02d", seconds / 60) + ":" + String.format("%02d", seconds % 60));
// format the textview to show the easily readable format
}
#Override
public void onFinish() {
x++;
// this function will be called when the timecount is finished
textViewShowTime.setText("SHOT!!!");
textViewShowTime.setVisibility(View.VISIBLE);
}
}
.start();
}
});
}
private void getReferenceOfViews() {
buttonStartTime = (Button) findViewById(R.id.btnStartTime);
textViewShowTime = (TextView) findViewById(R.id.tvTimeCount);
}
}`
totalTimeCountInMilliseconds =1000 * 1000; // time count for 3 minutes = 180 seconds
timeBlinkInMilliseconds = 10 * 1000; // blink starts at 1 minutes = 60 seconds
new CountDownTimer(totalTimeCountInMilliseconds , timeBlinkInMilliseconds )
above will call onTick 100 times

setText not displaying the value to a TextView

Let me preface that I'm new to programming for Android. I've been doing my due diligence to research to no avail. I have the source code below and I'm having issues with returning the value for returning.
I have the code laid out on eclipse and it's not triggering any errors. But when I build the code below, it comes back with an error. After inspecting the values in debug view, I can see the proper values just not binded to the TextView.
public class MyFirstActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
calculateResult(2012, 9, 29);
}
private void calculateResult(int year, int month, int day) {
long days = 0L;
int returning = 0;
java.util.Calendar cal = new java.util.GregorianCalendar(year, month-1, day);
long todayMI = new java.util.Date().getTime();
long calMI = cal.getTimeInMillis();
long millDiff = calMI - todayMI;
if (millDiff < 0) {
returning = 0;
} else {
days = millDiff / 1000 / 60 / 60;
returning = (int) Math.ceil(days / 24f);
}
TextView days_int_remaining = (TextView) findViewById(R.id.days_int_remaining);
days_int_remaining.setText(returning);
}
}
If it helps, here's the TextView on my layout XML:
<TextView
android:id="#+id/days_int_remaining"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center_horizontal|center_vertical"
android:shadowColor="#5000"
android:shadowDx="4"
android:shadowDy="3"
android:shadowRadius="1"
android:textColor="#fff"
android:textSize="70dip"
android:textStyle="bold"
/>
It may be something simple that I'm missing. Anything to help me further understand the code is appreciated!
I believe that the error you get is "runtime-error - close application".
If you look at method setText(...) that is overloaded you will see that it takes arguments with CharSequence (this is probably what you want) and int resId (this is what you provide).
resId - will seek if there is an resource in /values/strings.xml with given name attribute.
The resolution is to provide String:
days_int_remaining.setText(String.valueOf(returning));
just use
TextView days_int_remaining = (TextView) findViewById(R.id.days_int_remaining);
days_int_remaining.setText(String.valueOf(returning));
instead of
TextView days_int_remaining = (TextView) findViewById(R.id.days_int_remaining);
days_int_remaining.setText(returning);
Turn your returning int into a string.
Try this code :
Basically you have to convert your int value to a string before calling setText();
public class MyFirstActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TextView days_int_remaining = (TextView) findViewById(R.id.days_int_remaining);
int resultat = calculateResult(2012, 9, 29);
days_int_remaining.setText(Integer.toString(resultat));
}
private int calculateResult(int year, int month, int day) {
long days = 0L;
int returning = 0;
java.util.Calendar cal = new java.util.GregorianCalendar(year, month-1, day);
long todayMI = new java.util.Date().getTime();
long calMI = cal.getTimeInMillis();
long millDiff = calMI - todayMI;
if (millDiff < 0) {
returning = 0;
} else {
days = millDiff / 1000 / 60 / 60;
returning = (int) Math.ceil(days / 24f);
}
return returning;
}
}

Getting ongoing time from first view in android

I have been trying to setting up a function, which is getting the time since the window is appearing for the viewer.
I have been using a handler, timer, currenttimemillis.
That i have succeeded in doing. The window is showing the ongoing time since the window has been shown for the user - update frequency of 40fps, and i love how it looks and works..
BUT!
When going backwards and forwards into the window, the time restarts.
I want the window to show the (running) time since the initial view for the user and i don't want the time "broken" even if the program is stopped.
I am using sharedpreferences for saving strings and i have tried to saving the initial System.currentTimeMillis(); as a sharedpreference (as a long) and loading it again on oncreate with this if statement
SharedPreferences sData14;
private final int REFRESH_RATE = 25;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.orderwaiting);
setupVariables();
sData14 = getSharedPreferences(filename, 0);
}
private void setupVariables() {
mHandler.removeCallbacks(startTimer);
mHandler.postDelayed(startTimer, 0);
if (sData14 == null) {
starttime = System.currentTimeMillis();
sData14 = getSharedPreferences(filename, 0);
SharedPreferences.Editor editor = sData14.edit();
editor.putLong("sharedString14", starttime);
editor.commit();
};
if (sData14 != null) {
Long value_long = sData14.getLong("sharedString14", 1000000);
starttime = value_long;
};
EDITED ADDED
I don't use onresume and onpause.
Do i have to do that ?
I would expect the if statement handles the "case".. and i need a function that handles the case in which the program has been "closed", but the function is still needed to track how far we have reached in time.
Is it better to doinbackground, and be able to call it from another place as well ?
It just doesn't load the shared data...
In other words, i need a dynamic number to use from when the user first is viewing this window. Maybe there are other ways i could do this as well ??
I guess it should be working "this way".. but i can't seem to make it work..
I hope i have set up my problem in an simple way.
SOLUTION
long startTime;
private final int REFRESH_RATE = 25;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.orderwaiting);
setupVariables();
}
private void setupVariables() {
SharedPreferences sData14 = getSharedPreferences(filename, 0);
startTime = sData14.getLong("sharedString14", -1);
**starttime = startTime;**
if(startTime == -1){
//startTime not previously saved
starttime = System.currentTimeMillis();
SharedPreferences.Editor editor = sData14.edit();
editor.putLong("sharedString14", starttime);
editor.commit();
}
mHandler.removeCallbacks(startTimer);
mHandler.postDelayed(startTimer, 0);
i added the starttime = startTime, since it would else not load any starttime on the second view..
Thanks to David.. You saved my day :-)
Ah Ok, I missunderstood your initial question. The way you are checking your prefenences isn't quite right, you can also simplify it quite a bit. The code below should fix your problems:
long startTime;
private final int REFRESH_RATE = 25;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.orderwaiting);
setupVariables();
}
private void setupVariables() {
SharedPreferences sData14 = getSharedPreferences(filename, 0);
startTime = sData14.getLong("sharedString14", -1);
if(startTime == -1){
//startTime not previously saved
starttime = System.currentTimeMillis();
SharedPreferences.Editor editor = sData14.edit();
editor.putLong("sharedString14", starttime);
editor.commit();
}
mHandler.removeCallbacks(startTimer);
mHandler.postDelayed(startTimer, 0);
}

Android Studio When clicking on imageview reduce time from the timer

This is the first time I am posting something on StackOverflow and that it only because I felt like this is one of those times I was not able to find what I needed from the vast Internet!
I am currently making an Android Application that is a game where I have an ball that is an imageview and each time I click on the ball, it moves to a random location with this method I've built:
private void changePos() {
RelativeLayout gameLayout = findViewById(R.id.gameFrame);
int frameHeight = gameLayout.getHeight();
int frameWidth = gameLayout.getWidth();
Random ballLocationY = new Random();
int ballY = ballLocationY.nextInt(frameHeight);
Random ballLocationX = new Random();
int ballX = ballLocationX.nextInt(frameWidth);
gameLayout.setY(ballY);
gameLayout.setX(ballX);
}
So what I am struggling with is to reduce the timer with each click.
What I am trying to do is to reduce the timer with each click on the ball by a certain percentage.
Basically each time the user clicks on the ball, the timer starts off from 0 and counting upwards in seconds and milliseconds, and at 3 second mark the game is over.
This is the approach I am playing around with:
Runnable updateThreadTimer = new Runnable() {
#Override
public void run() {
timeInMilliseconds = System.Clock.uptimeMillis() - startTime();
updateTime = timeSwapBuff + timeInMilliseconds;
int secs = (int) (updateTime / 1000);
countDownText.setText("" + String.format("%2d", secs) + ":"
+ String.format("%3d", timeInMilliseconds));
mHandler.postDelayed(this, 0);
if (secs == 3 && timeInMilliseconds >= 3000) {
timeSwapBuff += timeInMilliseconds;
mHandler.removeCallbacks(updateThreadTimer);
}
}
}
Variables necessary:
long startTime = 0L, timeInMilliseconds = 0L, timeSwapBuff = 0L, updateTime = 0L;
Handler mHandler = new Handler();
And then I have this in the Overridden onCreate method:
ball.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
changePos();
startTime = SystemClock.uptimeMillis();
mHandler.postDelayed(updateThreadTimer, 0);
}
return true;
}
}
So what I get is a timer that gets reset to 0 with each click on the button and starts again. And the timer displays in seconds and milliseconds: 0:0000, which is what I wanted, but I wanna reduce a certain percentage with each click and update the time.
So let's say the counter starts with 3 seconds limit, after clicking on the ball couple of times, the timer should eventually come down and the game should fail if the timer goes above that time.
Obviously I have simplified the code quite a lot to get straight to the point.
I was wondering if anyone has any suggestions on how I could approach the problem I have.
I also want to apologize if the question is not asked properly, being the first time posting, leave suggestions if theres any hatin' going on!
And I also want to mention it one more time, I've looked around for a solution like this, and the code I have put together is from lots of different sources, so I feel like I've done my research, and not being able to find something, hence I am turning to StackOverflow!
So I guess I am going to answer my own question as I have solved it now and also in hope that this may solve problems for some.
I redid some parts of the code and scratched the Runnable way, instead I took an approach that uses CountDownTimer class.
private long currentTime;
private long maxTimer = 3000;
private double fivePercent = .05;
private CountDownTimer myTimer = null;
protected void onCreate(Bundle savedInstanceState) {
// Some code...
// Assign 3000 to currentTime
currentTime = maxTimer;
ball.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
if (myTimer != null) {
// Cancel timer
myTimer.cancel();
// Calculating 5 % from maxTimer
long a = (long) (maxTimer * fivePercent);
maxTimer -= a;
// Storing new maxTimer in currentTime
currentTime = maxTimer;
}
myTimer = new CountDownTimer(currentTime, 1) {
#Override
public void onTick(long millisUntilFinished) {
countDownText.setText("Time: " + String.valueOf(millisUntilFinished));
currentTime = millisUntilFinished;
}
#Override
public void onFinish() {
Intent intent = new Intent(getApplicationContext(), ResultsActivity.class);
GameActivity.this.startActivity(intent);
}
}.start();
changePos();
scoreLabel.setText("Score: " + score++ + "");
}
return true;
}
});
}
I have simplified the code for demonstration purpose, but basically what I did was to create a CountDownTimer, and whenever I click on the ball, the CountDownTimer will get cancelled, and invoke a new one, before creating a new CountDownTimer it will calculate off 5 % and put the result into the new CountDownTimer.

Categories

Resources