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