2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.deskclock;
19 import android.app.Service;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.res.AssetFileDescriptor;
23 import android.content.res.Resources;
24 import android.media.AudioManager;
25 import android.media.MediaPlayer;
26 import android.media.MediaPlayer.OnErrorListener;
27 import android.media.RingtoneManager;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.IBinder;
32 import android.os.Message;
33 import android.os.Vibrator;
34 import android.telephony.PhoneStateListener;
35 import android.telephony.TelephonyManager;
38 * Manages alarms and vibe. Runs as a service so that it can continue to play
39 * if another activity overrides the AlarmAlert dialog.
41 public class AlarmKlaxon extends Service {
43 /** Play alarm up to 10 minutes before silencing */
44 private static final int ALARM_TIMEOUT_SECONDS = 10 * 60;
46 private static final long[] sVibratePattern = new long[] { 500, 500 };
48 private boolean mPlaying = false;
49 private Vibrator mVibrator;
50 private MediaPlayer mMediaPlayer;
51 private Alarm mCurrentAlarm;
52 private long mStartTime;
53 private TelephonyManager mTelephonyManager;
54 private int mInitialCallState;
57 private static final int KILLER = 1000;
58 private Handler mHandler = new Handler() {
59 public void handleMessage(Message msg) {
63 Log.v("*********** Alarm killer triggered ***********");
65 sendKillBroadcast((Alarm) msg.obj);
72 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
74 public void onCallStateChanged(int state, String ignored) {
75 // The user might already be in a call when the alarm fires. When
76 // we register onCallStateChanged, we get the initial in-call state
77 // which kills the alarm. Check against the initial call state so
78 // we don't kill the alarm during a call.
79 if (state != TelephonyManager.CALL_STATE_IDLE
80 && state != mInitialCallState) {
81 sendKillBroadcast(mCurrentAlarm);
88 public void onCreate() {
89 mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
90 // Listen for incoming calls to kill the alarm.
92 (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
93 mTelephonyManager.listen(
94 mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
95 AlarmAlertWakeLock.acquireCpuWakeLock(this);
99 public void onDestroy() {
101 // Stop listening for incoming calls.
102 mTelephonyManager.listen(mPhoneStateListener, 0);
103 AlarmAlertWakeLock.releaseCpuLock();
107 public IBinder onBind(Intent intent) {
112 public int onStartCommand(Intent intent, int flags, int startId) {
113 // No intent, tell the system not to restart us.
114 if (intent == null) {
116 return START_NOT_STICKY;
119 final Alarm alarm = intent.getParcelableExtra(
120 Alarms.ALARM_INTENT_EXTRA);
123 Log.v("AlarmKlaxon failed to parse the alarm from the intent");
125 return START_NOT_STICKY;
128 if (mCurrentAlarm != null) {
129 sendKillBroadcast(mCurrentAlarm);
133 mCurrentAlarm = alarm;
134 // Record the initial call state here so that the new alarm has the
136 mInitialCallState = mTelephonyManager.getCallState();
141 private void sendKillBroadcast(Alarm alarm) {
142 long millis = System.currentTimeMillis() - mStartTime;
143 int minutes = (int) Math.round(millis / 60000.0);
144 Intent alarmKilled = new Intent(Alarms.ALARM_KILLED);
145 alarmKilled.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm);
146 alarmKilled.putExtra(Alarms.ALARM_KILLED_TIMEOUT, minutes);
147 sendBroadcast(alarmKilled);
150 // Volume suggested by media team for in-call alarms.
151 private static final float IN_CALL_VOLUME = 0.125f;
153 private void play(Alarm alarm) {
154 // stop() checks to see if we are already playing.
158 Log.v("AlarmKlaxon.play() " + alarm.id + " alert " + alarm.alert);
162 Uri alert = alarm.alert;
163 // Fall back on the default alarm if the database does not have an
166 alert = RingtoneManager.getDefaultUri(
167 RingtoneManager.TYPE_ALARM);
169 Log.v("Using default alarm: " + alert.toString());
173 // TODO: Reuse mMediaPlayer instead of creating a new one and/or use
175 mMediaPlayer = new MediaPlayer();
176 mMediaPlayer.setOnErrorListener(new OnErrorListener() {
177 public boolean onError(MediaPlayer mp, int what, int extra) {
178 Log.e("Error occurred while playing audio.");
187 // Check if we are in a call. If we are, use the in-call alarm
188 // resource at a low volume to not disrupt the call.
189 if (mTelephonyManager.getCallState()
190 != TelephonyManager.CALL_STATE_IDLE) {
191 Log.v("Using the in-call alarm");
192 mMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME);
193 setDataSourceFromResource(getResources(), mMediaPlayer,
194 R.raw.in_call_alarm);
196 mMediaPlayer.setDataSource(this, alert);
198 startAlarm(mMediaPlayer);
199 } catch (Exception ex) {
200 Log.v("Using the fallback ringtone");
201 // The alert may be on the sd card which could be busy right
202 // now. Use the fallback ringtone.
204 // Must reset the media player to clear the error state.
205 mMediaPlayer.reset();
206 setDataSourceFromResource(getResources(), mMediaPlayer,
208 startAlarm(mMediaPlayer);
209 } catch (Exception ex2) {
210 // At this point we just don't play anything.
211 Log.e("Failed to play fallback ringtone", ex2);
216 /* Start the vibrator after everything is ok with the media player */
218 mVibrator.vibrate(sVibratePattern, 0);
225 mStartTime = System.currentTimeMillis();
228 // Do the common stuff when starting the alarm.
229 private void startAlarm(MediaPlayer player)
230 throws java.io.IOException, IllegalArgumentException,
231 IllegalStateException {
232 final AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
233 // do not play alarms if stream volume is 0
234 // (typically because ringer mode is silent).
235 if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) {
236 player.setAudioStreamType(AudioManager.STREAM_ALARM);
237 player.setLooping(true);
243 private void setDataSourceFromResource(Resources resources,
244 MediaPlayer player, int res) throws java.io.IOException {
245 AssetFileDescriptor afd = resources.openRawResourceFd(res);
247 player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
254 * Stops alarm audio and disables alarm if it not snoozed and not
258 if (Log.LOGV) Log.v("AlarmKlaxon.stop()");
262 // Stop audio playing
263 if (mMediaPlayer != null) {
265 mMediaPlayer.release();
276 * Kills alarm audio after ALARM_TIMEOUT_SECONDS, so the alarm
279 * This just cancels the audio, but leaves the notification
280 * popped, so the user will know that the alarm tripped.
282 private void enableKiller(Alarm alarm) {
283 mHandler.sendMessageDelayed(mHandler.obtainMessage(KILLER, alarm),
284 1000 * ALARM_TIMEOUT_SECONDS);
287 private void disableKiller() {
288 mHandler.removeMessages(KILLER);