1/*
2 * Copyright (C) 2010 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
17package com.example.android.newalarm;
18
19import android.app.Notification;
20import android.app.NotificationManager;
21import android.app.PendingIntent;
22import android.app.Service;
23import android.content.Intent;
24import android.os.Binder;
25import android.os.IBinder;
26import android.os.Parcel;
27import android.os.RemoteException;
28import android.widget.Toast;
29
30/**
31 * <p>
32 * This class implements a service. The service is started by AlarmActivity, which contains a
33 * repeating countdown timer that sends a PendingIntent. The user starts and stops the timer with
34 * buttons in the UI.
35 * </p>
36 * <p>
37 * When this service is started, it creates a Runnable and starts it in a new Thread. The
38 * Runnable does a synchronized lock on the service's Binder object for 15 seconds, then issues
39 * a stopSelf(). The net effect is a new worker thread that takes 15 seconds to run and then
40 * shuts down the entire service. The activity restarts the service after 15 more seconds, when the
41 * countdown timer triggers again.
42 * </p>
43 * <p>
44 * This service is provided as the service under test for the sample test application
45 * AlarmServiceTest.
46 * </p>
47 * <p>
48 * Note: Since this sample is based on the Android 1.5 platform, it does not implement
49 * onStartCommand. See the Javadoc for android.app.Service for more details.
50 * </p>
51 */
52public class AlarmService extends Service {
53    // Defines a label for the thread that this service starts
54    private static final String ALARM_SERVICE_THREAD = "AlarmService";
55
56    // Defines 15 seconds
57    public static final long WAIT_TIME_SECONDS = 15;
58
59    // Define the number of milliseconds in one second
60    public static final long MILLISECS_PER_SEC = 1000;
61
62    /*
63     * For testing purposes, the following variables are defined as fields and set to
64     * package visibility.
65     */
66
67    // The NotificationManager used to send notifications to the status bar.
68    NotificationManager mNotificationManager;
69
70    // An Intent that displays the client if the user clicks the notification.
71    PendingIntent mContentIntent;
72
73    // A Notification to send to the Notification Manager when the service is started.
74    Notification mNotification;
75
76    // A Binder, used as the lock object for the worker thread.
77    IBinder mBinder = new AlarmBinder();
78
79    // A Thread object that will run the background task
80    Thread mWorkThread;
81
82    // The Runnable that is the service's "task". This illustrates how a service is used to
83    // offload work from a client.
84    Runnable mWorkTask = new Runnable() {
85        public void run() {
86            // Sets the wait time to 15 seconds, simulating a 15-second background task.
87            long waitTime = System.currentTimeMillis() + WAIT_TIME_SECONDS * MILLISECS_PER_SEC;
88
89            // Puts the wait in a while loop to ensure that it actually waited 15 seconds.
90            // This covers the situation where an interrupt might have overridden the wait.
91            while (System.currentTimeMillis() < waitTime) {
92                // Waits for 15 seconds or interruption
93                synchronized (mBinder) {
94                    try {
95                        // Waits for 15 seconds or until an interrupt triggers an exception.
96                        // If an interrupt occurs, the wait is recalculated to ensure a net
97                        // wait of 15 seconds.
98                        mBinder.wait(waitTime - System.currentTimeMillis());
99                    } catch (InterruptedException e) {
100                    }
101                }
102            }
103            // Stops the current service. In response, Android calls onDestroy().
104            stopSelf();
105        }
106    };
107
108    /**
109     *  Makes a full concrete subclass of Binder, rather than doing it in line, for readability.
110     */
111    public class AlarmBinder extends Binder {
112        // Constructor. Calls the super constructor to set up the instance.
113        public AlarmBinder() {
114            super();
115        }
116
117        @Override
118        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
119            throws RemoteException {
120
121            // Call the parent method with the arguments passed in
122            return super.onTransact(code, data, reply, flags);
123        }
124    }
125
126    /**
127     * Initializes the service when it is first started by a call to startService() or
128     * bindService().
129     */
130    @Override
131    public void onCreate() {
132        // Gets a handle to the system mNotification service.
133        mNotificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
134
135        // Updates the status bar to indicate that this service is running.
136        showNotification();
137
138        // Creates a new thread. A new thread is used so that the service's work doesn't block
139        // anything on the calling client's thread. By default, a service runs in the same
140        // process and thread as the client that starts it.
141        mWorkThread = new Thread(
142            null,  // threadgroup (in this case, null)
143            mWorkTask, // the Runnable that will run in this thread
144            ALARM_SERVICE_THREAD
145        );
146        // Starts the thread
147        mWorkThread.start();
148    }
149
150    /**
151     * Stops the service in response to the stopSelf() issued when the wait is over. Other
152     * clients that use this service could stop it by issuing a stopService() or a stopSelf() on
153     * the service object.
154     */
155    @Override
156    public void onDestroy() {
157        // Cancels the status bar mNotification based on its ID, which is set in showNotification().
158        mNotificationManager.cancel(R.string.alarm_service_started);
159
160        // Sends a notification to the screen.
161        Toast.makeText(
162            this,  // the current context
163            R.string.alarm_service_finished,  // the message to show
164            Toast.LENGTH_LONG   // how long to keep the message on the screen
165        ).show();  // show the text
166    }
167
168    // Returns the service's binder object to clients that issue onBind().
169    @Override
170    public IBinder onBind(Intent intent) {
171        return mBinder;
172    }
173
174    /**
175     * Displays a notification in the status bar that this service is running. This method
176     * also creates an Intent for the AlarmActivity client and attaches it to the notification
177     * line. If the user clicks the line in the expanded status window, the Intent triggers
178     * AlarmActivity.
179     */
180    private void showNotification() {
181        // Sets the text to use for the status bar and status list views.
182        CharSequence notificationText = getText(R.string.alarm_service_started);
183
184        // Sets the icon, status bar text, and display time for the mNotification.
185        mNotification = new Notification(
186            R.drawable.stat_sample,  // the status icon
187            notificationText, // the status text
188            System.currentTimeMillis()  // the time stamp
189        );
190
191        // Sets up the Intent that starts AlarmActivity
192        mContentIntent = PendingIntent.getActivity(
193            this,  // Start the Activity in the current context
194            0,   // not used
195            new Intent(this, AlarmActivity.class),  // A new Intent for AlarmActivity
196            0  // Use an existing activity instance if available
197        );
198
199        // Creates a new content view for the mNotification. The view appears when the user
200        // shows the expanded status window.
201        mNotification.setLatestEventInfo(
202            this,  //  Put the content view in the current context
203            getText(R.string.alarm_service_label),  // The text to use as the label of the entry
204            notificationText,  // The text to use as the contents of the entry
205            mContentIntent  // The intent to send when the entry is clicked
206        );
207
208        // Sets a unique ID for the notification and sends it to NotificationManager to be
209        // displayed. The ID is the integer marker for the notification string, which is
210        // guaranteed to be unique within the entire application.
211        mNotificationManager.notify(
212            R.string.alarm_service_started,  // unique id for the mNotification
213            mNotification   // the mNotification object
214        );
215    }
216}
217