1 /* 2 * Copyright (C) 2008 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 17 package com.android.deskclock; 18 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; 36 37 /** 38 * Manages alarms and vibe. Runs as a service so that it can continue to play 39 * if another activity overrides the AlarmAlert dialog. 40 */ 41 public class AlarmKlaxon extends Service { 42 43 /** Play alarm up to 10 minutes before silencing */ 44 private static final int ALARM_TIMEOUT_SECONDS = 10 * 60; 45 46 private static final long[] sVibratePattern = new long[] { 500, 500 }; 47 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; 55 56 // Internal messages 57 private static final int KILLER = 1000; 58 private Handler mHandler = new Handler() { 59 public void handleMessage(Message msg) { 60 switch (msg.what) { 61 case KILLER: 62 if (Log.LOGV) { 63 Log.v("*********** Alarm killer triggered ***********"); 64 } 65 sendKillBroadcast((Alarm) msg.obj); 66 stopSelf(); 67 break; 68 } 69 } 70 }; 71 72 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 73 @Override 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); 82 stopSelf(); 83 } 84 } 85 }; 86 87 @Override onCreate()88 public void onCreate() { 89 mVibrator = new Vibrator(); 90 // Listen for incoming calls to kill the alarm. 91 mTelephonyManager = 92 (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); 93 mTelephonyManager.listen( 94 mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); 95 AlarmAlertWakeLock.acquireCpuWakeLock(this); 96 } 97 98 @Override onDestroy()99 public void onDestroy() { 100 stop(); 101 // Stop listening for incoming calls. 102 mTelephonyManager.listen(mPhoneStateListener, 0); 103 AlarmAlertWakeLock.releaseCpuLock(); 104 } 105 106 @Override onBind(Intent intent)107 public IBinder onBind(Intent intent) { 108 return null; 109 } 110 111 @Override onStartCommand(Intent intent, int flags, int startId)112 public int onStartCommand(Intent intent, int flags, int startId) { 113 // No intent, tell the system not to restart us. 114 if (intent == null) { 115 stopSelf(); 116 return START_NOT_STICKY; 117 } 118 119 final Alarm alarm = intent.getParcelableExtra( 120 Alarms.ALARM_INTENT_EXTRA); 121 122 if (alarm == null) { 123 Log.v("AlarmKlaxon failed to parse the alarm from the intent"); 124 stopSelf(); 125 return START_NOT_STICKY; 126 } 127 128 if (mCurrentAlarm != null) { 129 sendKillBroadcast(mCurrentAlarm); 130 } 131 132 play(alarm); 133 mCurrentAlarm = alarm; 134 // Record the initial call state here so that the new alarm has the 135 // newest state. 136 mInitialCallState = mTelephonyManager.getCallState(); 137 138 return START_STICKY; 139 } 140 sendKillBroadcast(Alarm alarm)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); 148 } 149 150 // Volume suggested by media team for in-call alarms. 151 private static final float IN_CALL_VOLUME = 0.125f; 152 play(Alarm alarm)153 private void play(Alarm alarm) { 154 // stop() checks to see if we are already playing. 155 stop(); 156 157 if (Log.LOGV) { 158 Log.v("AlarmKlaxon.play() " + alarm.id + " alert " + alarm.alert); 159 } 160 161 if (!alarm.silent) { 162 Uri alert = alarm.alert; 163 // Fall back on the default alarm if the database does not have an 164 // alarm stored. 165 if (alert == null) { 166 alert = RingtoneManager.getDefaultUri( 167 RingtoneManager.TYPE_ALARM); 168 if (Log.LOGV) { 169 Log.v("Using default alarm: " + alert.toString()); 170 } 171 } 172 173 // TODO: Reuse mMediaPlayer instead of creating a new one and/or use 174 // RingtoneManager. 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."); 179 mp.stop(); 180 mp.release(); 181 mMediaPlayer = null; 182 return true; 183 } 184 }); 185 186 try { 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); 195 } else { 196 mMediaPlayer.setDataSource(this, alert); 197 } 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. 203 try { 204 // Must reset the media player to clear the error state. 205 mMediaPlayer.reset(); 206 setDataSourceFromResource(getResources(), mMediaPlayer, 207 com.android.internal.R.raw.fallbackring); 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); 212 } 213 } 214 } 215 216 /* Start the vibrator after everything is ok with the media player */ 217 if (alarm.vibrate) { 218 mVibrator.vibrate(sVibratePattern, 0); 219 } else { 220 mVibrator.cancel(); 221 } 222 223 enableKiller(alarm); 224 mPlaying = true; 225 mStartTime = System.currentTimeMillis(); 226 } 227 228 // Do the common stuff when starting the alarm. startAlarm(MediaPlayer player)229 private void startAlarm(MediaPlayer player) 230 throws java.io.IOException, IllegalArgumentException, 231 IllegalStateException { 232 player.setAudioStreamType(AudioManager.STREAM_ALARM); 233 player.setLooping(true); 234 player.prepare(); 235 player.start(); 236 } 237 setDataSourceFromResource(Resources resources, MediaPlayer player, int res)238 private void setDataSourceFromResource(Resources resources, 239 MediaPlayer player, int res) throws java.io.IOException { 240 AssetFileDescriptor afd = resources.openRawResourceFd(res); 241 if (afd != null) { 242 player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), 243 afd.getLength()); 244 afd.close(); 245 } 246 } 247 248 /** 249 * Stops alarm audio and disables alarm if it not snoozed and not 250 * repeating 251 */ stop()252 public void stop() { 253 if (Log.LOGV) Log.v("AlarmKlaxon.stop()"); 254 if (mPlaying) { 255 mPlaying = false; 256 257 // Stop audio playing 258 if (mMediaPlayer != null) { 259 mMediaPlayer.stop(); 260 mMediaPlayer.release(); 261 mMediaPlayer = null; 262 } 263 264 // Stop vibrator 265 mVibrator.cancel(); 266 } 267 disableKiller(); 268 } 269 270 /** 271 * Kills alarm audio after ALARM_TIMEOUT_SECONDS, so the alarm 272 * won't run all day. 273 * 274 * This just cancels the audio, but leaves the notification 275 * popped, so the user will know that the alarm tripped. 276 */ enableKiller(Alarm alarm)277 private void enableKiller(Alarm alarm) { 278 mHandler.sendMessageDelayed(mHandler.obtainMessage(KILLER, alarm), 279 1000 * ALARM_TIMEOUT_SECONDS); 280 } 281 disableKiller()282 private void disableKiller() { 283 mHandler.removeMessages(KILLER); 284 } 285 286 287 } 288