1 /* 2 * Copyright (C) 2012 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.IBinder; 30 import android.telephony.PhoneStateListener; 31 import android.telephony.TelephonyManager; 32 33 /** 34 * Play the timer's ringtone. Will continue playing the same alarm until service is stopped. 35 */ 36 public class TimerRingService extends Service { 37 38 private boolean mPlaying = false; 39 private MediaPlayer mMediaPlayer; 40 private TelephonyManager mTelephonyManager; 41 private int mInitialCallState; 42 43 44 private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 45 @Override 46 public void onCallStateChanged(int state, String ignored) { 47 // The user might already be in a call when the alarm fires. When 48 // we register onCallStateChanged, we get the initial in-call state 49 // which kills the alarm. Check against the initial call state so 50 // we don't kill the alarm during a call. 51 if (state != TelephonyManager.CALL_STATE_IDLE 52 && state != mInitialCallState) { 53 stopSelf(); 54 } 55 } 56 }; 57 58 @Override 59 public void onCreate() { 60 // Listen for incoming calls to kill the alarm. 61 mTelephonyManager = 62 (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); 63 mTelephonyManager.listen( 64 mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); 65 AlarmAlertWakeLock.acquireScreenCpuWakeLock(this); 66 } 67 68 @Override 69 public void onDestroy() { 70 stop(); 71 // Stop listening for incoming calls. 72 mTelephonyManager.listen(mPhoneStateListener, 0); 73 AlarmAlertWakeLock.releaseCpuLock(); 74 } 75 76 @Override 77 public IBinder onBind(Intent intent) { 78 return null; 79 } 80 81 @Override 82 public int onStartCommand(Intent intent, int flags, int startId) { 83 // No intent, tell the system not to restart us. 84 if (intent == null) { 85 stopSelf(); 86 return START_NOT_STICKY; 87 } 88 89 play(); 90 // Record the initial call state here so that the new alarm has the 91 // newest state. 92 mInitialCallState = mTelephonyManager.getCallState(); 93 94 return START_STICKY; 95 } 96 97 // Volume suggested by media team for in-call alarms. 98 private static final float IN_CALL_VOLUME = 0.125f; 99 100 private void play() { 101 102 if (mPlaying) { 103 return; 104 } 105 106 if (Log.LOGV) { 107 Log.v("TimerRingService.play()"); 108 } 109 110 // TODO: Reuse mMediaPlayer instead of creating a new one and/or use 111 // RingtoneManager. 112 mMediaPlayer = new MediaPlayer(); 113 mMediaPlayer.setOnErrorListener(new OnErrorListener() { 114 @Override 115 public boolean onError(MediaPlayer mp, int what, int extra) { 116 Log.e("Error occurred while playing audio."); 117 mp.stop(); 118 mp.release(); 119 mMediaPlayer = null; 120 return true; 121 } 122 }); 123 124 try { 125 // Check if we are in a call. If we are, use the in-call alarm 126 // resource at a low volume to not disrupt the call. 127 if (mTelephonyManager.getCallState() 128 != TelephonyManager.CALL_STATE_IDLE) { 129 Log.v("Using the in-call alarm"); 130 mMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME); 131 setDataSourceFromResource(getResources(), mMediaPlayer, 132 R.raw.in_call_alarm); 133 } else { 134 AssetFileDescriptor afd = getAssets().openFd("sounds/Timer_Expire.ogg"); 135 mMediaPlayer.setDataSource( 136 afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); 137 } 138 startAlarm(mMediaPlayer); 139 } catch (Exception ex) { 140 Log.v("Using the fallback ringtone"); 141 // The alert may be on the sd card which could be busy right 142 // now. Use the fallback ringtone. 143 try { 144 // Must reset the media player to clear the error state. 145 mMediaPlayer.reset(); 146 setDataSourceFromResource(getResources(), mMediaPlayer, 147 R.raw.fallbackring); 148 startAlarm(mMediaPlayer); 149 } catch (Exception ex2) { 150 // At this point we just don't play anything. 151 Log.e("Failed to play fallback ringtone", ex2); 152 } 153 } 154 155 mPlaying = true; 156 } 157 158 // Do the common stuff when starting the alarm. 159 private void startAlarm(MediaPlayer player) 160 throws java.io.IOException, IllegalArgumentException, 161 IllegalStateException { 162 final AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); 163 // do not play alarms if stream volume is 0 164 // (typically because ringer mode is silent). 165 if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) { 166 player.setAudioStreamType(AudioManager.STREAM_ALARM); 167 player.setLooping(true); 168 player.prepare(); 169 player.start(); 170 } 171 } 172 173 private void setDataSourceFromResource(Resources resources, 174 MediaPlayer player, int res) throws java.io.IOException { 175 AssetFileDescriptor afd = resources.openRawResourceFd(res); 176 if (afd != null) { 177 player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), 178 afd.getLength()); 179 afd.close(); 180 } 181 } 182 183 /** 184 * Stops timer audio 185 */ 186 public void stop() { 187 if (Log.LOGV) Log.v("AlarmKlaxon.stop()"); 188 if (mPlaying) { 189 mPlaying = false; 190 191 // Stop audio playing 192 if (mMediaPlayer != null) { 193 mMediaPlayer.stop(); 194 mMediaPlayer.release(); 195 mMediaPlayer = null; 196 } 197 } 198 } 199 200 201 } 202