-
-
Save kinsleykajiva/0f068cbc4e4b2cc1dd072fb44bc518c7 to your computer and use it in GitHub Desktop.
Example of how to create a long running timer service that survives activity destruction by moving to the foreground, and back to the background when a new activity bind to it.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Copyright 2015 Google Inc. All Rights Reserved. | |
// | |
// 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.example.google.timerservice; | |
import android.app.Notification; | |
import android.app.PendingIntent; | |
import android.app.Service; | |
import android.content.ComponentName; | |
import android.content.Intent; | |
import android.content.ServiceConnection; | |
import android.os.Binder; | |
import android.os.Bundle; | |
import android.os.Handler; | |
import android.os.IBinder; | |
import android.os.Message; | |
import android.support.v4.app.NotificationCompat; | |
import android.support.v7.app.AppCompatActivity; | |
import android.util.Log; | |
import android.view.View; | |
import android.widget.Button; | |
import android.widget.TextView; | |
import java.lang.ref.WeakReference; | |
/** | |
* Example activity to manage a long-running timer, which survives the destruction of the activity | |
* by using a foreground service and notification | |
* | |
* Add the following to the manifest: | |
* <service android:name=".MainActivity$TimerService" android:exported="false" /> | |
*/ | |
public class TimerActivity extends AppCompatActivity { | |
private static final String TAG = TimerActivity.class.getSimpleName(); | |
private TimerService timerService; | |
private boolean serviceBound; | |
private Button timerButton; | |
private TextView timerTextView; | |
// Handler to update the UI every second when the timer is running | |
private final Handler mUpdateTimeHandler = new UIUpdateHandler(this); | |
// Message type for the handler | |
private final static int MSG_UPDATE_TIME = 0; | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
timerButton = (Button)findViewById(R.id.timer_button); | |
timerTextView = (TextView)findViewById(R.id.timer_text_view); | |
} | |
@Override | |
protected void onStart() { | |
super.onStart(); | |
if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
Log.v(TAG, "Starting and binding service"); | |
} | |
Intent i = new Intent(this, TimerService.class); | |
startService(i); | |
bindService(i, mConnection, 0); | |
} | |
@Override | |
protected void onStop() { | |
super.onStop(); | |
updateUIStopRun(); | |
if (serviceBound) { | |
// If a timer is active, foreground the service, otherwise kill the service | |
if (timerService.isTimerRunning()) { | |
timerService.foreground(); | |
} | |
else { | |
stopService(new Intent(this, TimerService.class)); | |
} | |
// Unbind the service | |
unbindService(mConnection); | |
serviceBound = false; | |
} | |
} | |
public void runButtonClick(View v) { | |
if (serviceBound && !timerService.isTimerRunning()) { | |
if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
Log.v(TAG, "Starting timer"); | |
} | |
timerService.startTimer(); | |
updateUIStartRun(); | |
} | |
else if (serviceBound && timerService.isTimerRunning()) { | |
if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
Log.v(TAG, "Stopping timer"); | |
} | |
timerService.stopTimer(); | |
updateUIStopRun(); | |
} | |
} | |
/** | |
* Updates the UI when a run starts | |
*/ | |
private void updateUIStartRun() { | |
mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME); | |
timerButton.setText(R.string.timer_stop_button); | |
} | |
/** | |
* Updates the UI when a run stops | |
*/ | |
private void updateUIStopRun() { | |
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME); | |
timerButton.setText(R.string.timer_start_button); | |
} | |
/** | |
* Updates the timer readout in the UI; the service must be bound | |
*/ | |
private void updateUITimer() { | |
if (serviceBound) { | |
timerTextView.setText(timerService.elapsedTime() + " seconds"); | |
} | |
} | |
/** | |
* Callback for service binding, passed to bindService() | |
*/ | |
private ServiceConnection mConnection = new ServiceConnection() { | |
@Override | |
public void onServiceConnected(ComponentName className, IBinder service) { | |
if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
Log.v(TAG, "Service bound"); | |
} | |
TimerService.RunServiceBinder binder = (TimerService.RunServiceBinder) service; | |
timerService = binder.getService(); | |
serviceBound = true; | |
// Ensure the service is not in the foreground when bound | |
timerService.background(); | |
// Update the UI if the service is already running the timer | |
if (timerService.isTimerRunning()) { | |
updateUIStartRun(); | |
} | |
} | |
@Override | |
public void onServiceDisconnected(ComponentName name) { | |
if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
Log.v(TAG, "Service disconnect"); | |
} | |
serviceBound = false; | |
} | |
}; | |
/** | |
* When the timer is running, use this handler to update | |
* the UI every second to show timer progress | |
*/ | |
static class UIUpdateHandler extends Handler { | |
private final static int UPDATE_RATE_MS = 1000; | |
private final WeakReference<TimerActivity> activity; | |
UIUpdateHandler(TimerActivity activity) { | |
this.activity = new WeakReference<>(activity); | |
} | |
@Override | |
public void handleMessage(Message message) { | |
if (MSG_UPDATE_TIME == message.what) { | |
if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
Log.v(TAG, "updating time"); | |
} | |
activity.get().updateUITimer(); | |
sendEmptyMessageDelayed(MSG_UPDATE_TIME, UPDATE_RATE_MS); | |
} | |
} | |
} | |
/** | |
* Timer service tracks the start and end time of timer; service can be placed into the | |
* foreground to prevent it being killed when the activity goes away | |
*/ | |
public static class TimerService extends Service { | |
private static final String TAG = TimerService.class.getSimpleName(); | |
// Start and end times in milliseconds | |
private long startTime, endTime; | |
// Is the service tracking time? | |
private boolean isTimerRunning; | |
// Foreground notification id | |
private static final int NOTIFICATION_ID = 1; | |
// Service binder | |
private final IBinder serviceBinder = new RunServiceBinder(); | |
public class RunServiceBinder extends Binder { | |
TimerService getService() { | |
return TimerService.this; | |
} | |
} | |
@Override | |
public void onCreate() { | |
if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
Log.v(TAG, "Creating service"); | |
} | |
startTime = 0; | |
endTime = 0; | |
isTimerRunning = false; | |
} | |
@Override | |
public int onStartCommand(Intent intent, int flags, int startId) { | |
if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
Log.v(TAG, "Starting service"); | |
} | |
return Service.START_STICKY; | |
} | |
@Override | |
public IBinder onBind(Intent intent) { | |
if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
Log.v(TAG, "Binding service"); | |
} | |
return serviceBinder; | |
} | |
@Override | |
public void onDestroy() { | |
super.onDestroy(); | |
if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
Log.v(TAG, "Destroying service"); | |
} | |
} | |
/** | |
* Starts the timer | |
*/ | |
public void startTimer() { | |
if (!isTimerRunning) { | |
startTime = System.currentTimeMillis(); | |
isTimerRunning = true; | |
} | |
else { | |
Log.e(TAG, "startTimer request for an already running timer"); | |
} | |
} | |
/** | |
* Stops the timer | |
*/ | |
public void stopTimer() { | |
if (isTimerRunning) { | |
endTime = System.currentTimeMillis(); | |
isTimerRunning = false; | |
} | |
else { | |
Log.e(TAG, "stopTimer request for a timer that isn't running"); | |
} | |
} | |
/** | |
* @return whether the timer is running | |
*/ | |
public boolean isTimerRunning() { | |
return isTimerRunning; | |
} | |
/** | |
* Returns the elapsed time | |
* | |
* @return the elapsed time in seconds | |
*/ | |
public long elapsedTime() { | |
// If the timer is running, the end time will be zero | |
return endTime > startTime ? | |
(endTime - startTime) / 1000 : | |
(System.currentTimeMillis() - startTime) / 1000; | |
} | |
/** | |
* Place the service into the foreground | |
*/ | |
public void foreground() { | |
startForeground(NOTIFICATION_ID, createNotification()); | |
} | |
/** | |
* Return the service to the background | |
*/ | |
public void background() { | |
stopForeground(true); | |
} | |
/** | |
* Creates a notification for placing the service into the foreground | |
* | |
* @return a notification for interacting with the service when in the foreground | |
*/ | |
private Notification createNotification() { | |
NotificationCompat.Builder builder = new NotificationCompat.Builder(this) | |
.setContentTitle("Timer Active") | |
.setContentText("Tap to return to the timer") | |
.setSmallIcon(R.mipmap.ic_launcher); | |
Intent resultIntent = new Intent(this, TimerActivity.class); | |
PendingIntent resultPendingIntent = | |
PendingIntent.getActivity(this, 0, resultIntent, | |
PendingIntent.FLAG_UPDATE_CURRENT); | |
builder.setContentIntent(resultPendingIntent); | |
return builder.build(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment