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 17package com.android.deskclock; 18 19import android.app.Service; 20import android.content.Context; 21import android.content.Intent; 22import android.content.res.AssetFileDescriptor; 23import android.content.res.Resources; 24import android.media.AudioManager; 25import android.media.MediaPlayer; 26import android.media.MediaPlayer.OnErrorListener; 27import android.media.RingtoneManager; 28import android.net.Uri; 29import android.os.Handler; 30import android.os.IBinder; 31import android.os.Message; 32import android.os.Vibrator; 33import android.preference.PreferenceManager; 34import android.telephony.PhoneStateListener; 35import android.telephony.TelephonyManager; 36import android.text.format.DateUtils; 37 38/** 39 * Manages alarms and vibe. Runs as a service so that it can continue to play 40 * if another activity overrides the AlarmAlert dialog. 41 */ 42public class AlarmKlaxon extends Service { 43 // Default of 10 minutes until alarm is silenced. 44 private static final String DEFAULT_ALARM_TIMEOUT = "10"; 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 @Override 60 public void handleMessage(Message msg) { 61 switch (msg.what) { 62 case KILLER: 63 if (Log.LOGV) { 64 Log.v("*********** Alarm killer triggered ***********"); 65 } 66 sendKillBroadcast((Alarm) msg.obj, false); 67 stopSelf(); 68 break; 69 } 70 } 71 }; 72 73 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 74 @Override 75 public void onCallStateChanged(int state, String ignored) { 76 // The user might already be in a call when the alarm fires. When 77 // we register onCallStateChanged, we get the initial in-call state 78 // which kills the alarm. Check against the initial call state so 79 // we don't kill the alarm during a call. 80 if (state != TelephonyManager.CALL_STATE_IDLE 81 && state != mInitialCallState) { 82 sendKillBroadcast(mCurrentAlarm, false); 83 stopSelf(); 84 } 85 } 86 }; 87 88 @Override 89 public void onCreate() { 90 mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); 91 // Listen for incoming calls to kill the alarm. 92 mTelephonyManager = 93 (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); 94 mTelephonyManager.listen( 95 mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); 96 AlarmAlertWakeLock.acquireCpuWakeLock(this); 97 } 98 99 @Override 100 public void onDestroy() { 101 stop(); 102 Intent alarmDone = new Intent(Alarms.ALARM_DONE_ACTION); 103 sendBroadcast(alarmDone); 104 105 // Stop listening for incoming calls. 106 mTelephonyManager.listen(mPhoneStateListener, 0); 107 AlarmAlertWakeLock.releaseCpuLock(); 108 } 109 110 @Override 111 public IBinder onBind(Intent intent) { 112 return null; 113 } 114 115 @Override 116 public int onStartCommand(Intent intent, int flags, int startId) { 117 // No intent, tell the system not to restart us. 118 if (intent == null) { 119 stopSelf(); 120 return START_NOT_STICKY; 121 } 122 123 final Alarm alarm = intent.getParcelableExtra( 124 Alarms.ALARM_INTENT_EXTRA); 125 126 if (alarm == null) { 127 Log.v("AlarmKlaxon failed to parse the alarm from the intent"); 128 stopSelf(); 129 return START_NOT_STICKY; 130 } 131 132 if (mCurrentAlarm != null) { 133 sendKillBroadcast(mCurrentAlarm, true); 134 } 135 136 play(alarm); 137 mCurrentAlarm = alarm; 138 // Record the initial call state here so that the new alarm has the 139 // newest state. 140 mInitialCallState = mTelephonyManager.getCallState(); 141 142 return START_STICKY; 143 } 144 145 private void sendKillBroadcast(Alarm alarm, boolean replaced) { 146 long millis = System.currentTimeMillis() - mStartTime; 147 int minutes = (int) Math.round(millis / (double)DateUtils.MINUTE_IN_MILLIS); 148 Intent alarmKilled = new Intent(Alarms.ALARM_KILLED); 149 alarmKilled.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); 150 alarmKilled.putExtra(Alarms.ALARM_KILLED_TIMEOUT, minutes); 151 alarmKilled.putExtra(Alarms.ALARM_REPLACED, replaced); 152 sendBroadcast(alarmKilled); 153 } 154 155 // Volume suggested by media team for in-call alarms. 156 private static final float IN_CALL_VOLUME = 0.125f; 157 158 private void play(Alarm alarm) { 159 // stop() checks to see if we are already playing. 160 stop(); 161 162 if (Log.LOGV) { 163 Log.v("AlarmKlaxon.play() " + alarm.id + " alert " + alarm.alert); 164 } 165 166 if (!alarm.silent) { 167 Uri alert = alarm.alert; 168 // Fall back on the default alarm if the database does not have an 169 // alarm stored. 170 if (alert == null) { 171 alert = RingtoneManager.getDefaultUri( 172 RingtoneManager.TYPE_ALARM); 173 if (Log.LOGV) { 174 Log.v("Using default alarm: " + alert.toString()); 175 } 176 } 177 178 // TODO: Reuse mMediaPlayer instead of creating a new one and/or use 179 // RingtoneManager. 180 mMediaPlayer = new MediaPlayer(); 181 mMediaPlayer.setOnErrorListener(new OnErrorListener() { 182 @Override 183 public boolean onError(MediaPlayer mp, int what, int extra) { 184 Log.e("Error occurred while playing audio."); 185 mp.stop(); 186 mp.release(); 187 mMediaPlayer = null; 188 return true; 189 } 190 }); 191 192 try { 193 // Check if we are in a call. If we are, use the in-call alarm 194 // resource at a low volume to not disrupt the call. 195 if (mTelephonyManager.getCallState() 196 != TelephonyManager.CALL_STATE_IDLE) { 197 Log.v("Using the in-call alarm"); 198 mMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME); 199 setDataSourceFromResource(getResources(), mMediaPlayer, 200 R.raw.in_call_alarm); 201 } else { 202 mMediaPlayer.setDataSource(this, alert); 203 } 204 startAlarm(mMediaPlayer); 205 } catch (Exception ex) { 206 Log.v("Using the fallback ringtone"); 207 // The alert may be on the sd card which could be busy right 208 // now. Use the fallback ringtone. 209 try { 210 // Must reset the media player to clear the error state. 211 mMediaPlayer.reset(); 212 setDataSourceFromResource(getResources(), mMediaPlayer, 213 R.raw.fallbackring); 214 startAlarm(mMediaPlayer); 215 } catch (Exception ex2) { 216 // At this point we just don't play anything. 217 Log.e("Failed to play fallback ringtone", ex2); 218 } 219 } 220 } 221 222 /* Start the vibrator after everything is ok with the media player */ 223 if (alarm.vibrate) { 224 mVibrator.vibrate(sVibratePattern, 0); 225 } else { 226 mVibrator.cancel(); 227 } 228 229 enableKiller(alarm); 230 mPlaying = true; 231 mStartTime = System.currentTimeMillis(); 232 } 233 234 // Do the common stuff when starting the alarm. 235 private void startAlarm(MediaPlayer player) 236 throws java.io.IOException, IllegalArgumentException, 237 IllegalStateException { 238 final AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); 239 // do not play alarms if stream volume is 0 240 // (typically because ringer mode is silent). 241 if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) { 242 player.setAudioStreamType(AudioManager.STREAM_ALARM); 243 player.setLooping(true); 244 player.prepare(); 245 player.start(); 246 } 247 } 248 249 private void setDataSourceFromResource(Resources resources, 250 MediaPlayer player, int res) throws java.io.IOException { 251 AssetFileDescriptor afd = resources.openRawResourceFd(res); 252 if (afd != null) { 253 player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), 254 afd.getLength()); 255 afd.close(); 256 } 257 } 258 259 /** 260 * Stops alarm audio and disables alarm if it not snoozed and not 261 * repeating 262 */ 263 public void stop() { 264 if (Log.LOGV) Log.v("AlarmKlaxon.stop()"); 265 if (mPlaying) { 266 mPlaying = false; 267 268 // Stop audio playing 269 if (mMediaPlayer != null) { 270 mMediaPlayer.stop(); 271 mMediaPlayer.release(); 272 mMediaPlayer = null; 273 } 274 275 // Stop vibrator 276 mVibrator.cancel(); 277 } 278 disableKiller(); 279 } 280 281 /** 282 * Kills alarm audio after ALARM_TIMEOUT_SECONDS, so the alarm 283 * won't run all day. 284 * 285 * This just cancels the audio, but leaves the notification 286 * popped, so the user will know that the alarm tripped. 287 */ 288 private void enableKiller(Alarm alarm) { 289 final String autoSnooze = 290 PreferenceManager.getDefaultSharedPreferences(this) 291 .getString(SettingsActivity.KEY_AUTO_SILENCE, 292 DEFAULT_ALARM_TIMEOUT); 293 int autoSnoozeMinutes = Integer.parseInt(autoSnooze); 294 if (autoSnoozeMinutes != -1) { 295 mHandler.sendMessageDelayed(mHandler.obtainMessage(KILLER, alarm), 296 autoSnoozeMinutes * DateUtils.MINUTE_IN_MILLIS); 297 } 298 } 299 300 private void disableKiller() { 301 mHandler.removeMessages(KILLER); 302 } 303 304 305} 306