1/*
2 * Copyright (C) 2013 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.alarms;
18
19import android.content.Context;
20import android.content.res.AssetFileDescriptor;
21import android.media.AudioAttributes;
22import android.media.AudioManager;
23import android.media.MediaPlayer;
24import android.media.MediaPlayer.OnErrorListener;
25import android.media.RingtoneManager;
26import android.net.Uri;
27import android.os.Vibrator;
28
29import com.android.deskclock.LogUtils;
30import com.android.deskclock.R;
31import com.android.deskclock.provider.AlarmInstance;
32
33import java.io.IOException;
34
35/**
36 * Manages playing ringtone and vibrating the device.
37 */
38public class AlarmKlaxon {
39    private static final long[] sVibratePattern = new long[] { 500, 500 };
40
41    // Volume suggested by media team for in-call alarms.
42    private static final float IN_CALL_VOLUME = 0.125f;
43
44    private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
45            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
46            .setUsage(AudioAttributes.USAGE_ALARM)
47            .build();
48
49    private static boolean sStarted = false;
50    private static MediaPlayer sMediaPlayer = null;
51
52    public static void stop(Context context) {
53        LogUtils.v("AlarmKlaxon.stop()");
54
55        if (sStarted) {
56            sStarted = false;
57            // Stop audio playing
58            if (sMediaPlayer != null) {
59                sMediaPlayer.stop();
60                AudioManager audioManager = (AudioManager)
61                        context.getSystemService(Context.AUDIO_SERVICE);
62                audioManager.abandonAudioFocus(null);
63                sMediaPlayer.release();
64                sMediaPlayer = null;
65            }
66
67            ((Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE)).cancel();
68        }
69    }
70
71    public static void start(final Context context, AlarmInstance instance,
72            boolean inTelephoneCall) {
73        LogUtils.v("AlarmKlaxon.start()");
74        // Make sure we are stop before starting
75        stop(context);
76
77        if (!AlarmInstance.NO_RINGTONE_URI.equals(instance.mRingtone)) {
78            Uri alarmNoise = instance.mRingtone;
79            // Fall back on the default alarm if the database does not have an
80            // alarm stored.
81            if (alarmNoise == null) {
82                alarmNoise = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
83                LogUtils.v("Using default alarm: " + alarmNoise.toString());
84            }
85
86            // TODO: Reuse mMediaPlayer instead of creating a new one and/or use RingtoneManager.
87            sMediaPlayer = new MediaPlayer();
88            sMediaPlayer.setOnErrorListener(new OnErrorListener() {
89                @Override
90                public boolean onError(MediaPlayer mp, int what, int extra) {
91                    LogUtils.e("Error occurred while playing audio. Stopping AlarmKlaxon.");
92                    AlarmKlaxon.stop(context);
93                    return true;
94                }
95            });
96
97            try {
98                // Check if we are in a call. If we are, use the in-call alarm
99                // resource at a low volume to not disrupt the call.
100                if (inTelephoneCall) {
101                    LogUtils.v("Using the in-call alarm");
102                    sMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME);
103                    setDataSourceFromResource(context, sMediaPlayer, R.raw.in_call_alarm);
104                } else {
105                    sMediaPlayer.setDataSource(context, alarmNoise);
106                }
107                startAlarm(context, sMediaPlayer);
108            } catch (Exception ex) {
109                LogUtils.v("Using the fallback ringtone");
110                // The alarmNoise may be on the sd card which could be busy right
111                // now. Use the fallback ringtone.
112                try {
113                    // Must reset the media player to clear the error state.
114                    sMediaPlayer.reset();
115                    setDataSourceFromResource(context, sMediaPlayer, R.raw.fallbackring);
116                    startAlarm(context, sMediaPlayer);
117                } catch (Exception ex2) {
118                    // At this point we just don't play anything.
119                    LogUtils.e("Failed to play fallback ringtone", ex2);
120                }
121            }
122        }
123
124        if (instance.mVibrate) {
125            Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
126            vibrator.vibrate(sVibratePattern, 0, VIBRATION_ATTRIBUTES);
127        }
128
129        sStarted = true;
130    }
131
132    // Do the common stuff when starting the alarm.
133    private static void startAlarm(Context context, MediaPlayer player) throws IOException {
134        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
135        // do not play alarms if stream volume is 0 (typically because ringer mode is silent).
136        if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) {
137            player.setAudioStreamType(AudioManager.STREAM_ALARM);
138            player.setLooping(true);
139            player.prepare();
140            audioManager.requestAudioFocus(null,
141                    AudioManager.STREAM_ALARM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
142            player.start();
143        }
144    }
145
146    private static void setDataSourceFromResource(Context context, MediaPlayer player, int res)
147            throws IOException {
148        AssetFileDescriptor afd = context.getResources().openRawResourceFd(res);
149        if (afd != null) {
150            player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
151            afd.close();
152        }
153    }
154}
155