Home | History | Annotate | Download | only in deskclock
      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.preference.PreferenceManager;
     35 import android.telephony.PhoneStateListener;
     36 import android.telephony.TelephonyManager;
     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  */
     42 public 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         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
     88     public void onCreate() {
     89         mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
     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
     99     public void onDestroy() {
    100         stop();
    101         // Stop listening for incoming calls.
    102         mTelephonyManager.listen(mPhoneStateListener, 0);
    103         AlarmAlertWakeLock.releaseCpuLock();
    104     }
    105 
    106     @Override
    107     public IBinder onBind(Intent intent) {
    108         return null;
    109     }
    110 
    111     @Override
    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 
    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 
    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                             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.
    229     private void startAlarm(MediaPlayer player)
    230             throws java.io.IOException, IllegalArgumentException,
    231                    IllegalStateException {
    232         final AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
    233         // do not play alarms if stream volume is 0
    234         // (typically because ringer mode is silent).
    235         if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) {
    236             player.setAudioStreamType(AudioManager.STREAM_ALARM);
    237             player.setLooping(true);
    238             player.prepare();
    239             player.start();
    240         }
    241     }
    242 
    243     private void setDataSourceFromResource(Resources resources,
    244             MediaPlayer player, int res) throws java.io.IOException {
    245         AssetFileDescriptor afd = resources.openRawResourceFd(res);
    246         if (afd != null) {
    247             player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
    248                     afd.getLength());
    249             afd.close();
    250         }
    251     }
    252 
    253     /**
    254      * Stops alarm audio and disables alarm if it not snoozed and not
    255      * repeating
    256      */
    257     public void stop() {
    258         if (Log.LOGV) Log.v("AlarmKlaxon.stop()");
    259         if (mPlaying) {
    260             mPlaying = false;
    261 
    262             Intent alarmDone = new Intent(Alarms.ALARM_DONE_ACTION);
    263             sendBroadcast(alarmDone);
    264 
    265             // Stop audio playing
    266             if (mMediaPlayer != null) {
    267                 mMediaPlayer.stop();
    268                 mMediaPlayer.release();
    269                 mMediaPlayer = null;
    270             }
    271 
    272             // Stop vibrator
    273             mVibrator.cancel();
    274         }
    275         disableKiller();
    276     }
    277 
    278     /**
    279      * Kills alarm audio after ALARM_TIMEOUT_SECONDS, so the alarm
    280      * won't run all day.
    281      *
    282      * This just cancels the audio, but leaves the notification
    283      * popped, so the user will know that the alarm tripped.
    284      */
    285     private void enableKiller(Alarm alarm) {
    286         final String autoSnooze =
    287                 PreferenceManager.getDefaultSharedPreferences(this)
    288                 .getString(SettingsActivity.KEY_AUTO_SILENCE,
    289                         DEFAULT_ALARM_TIMEOUT);
    290         int autoSnoozeMinutes = Integer.parseInt(autoSnooze);
    291         if (autoSnoozeMinutes != -1) {
    292             mHandler.sendMessageDelayed(mHandler.obtainMessage(KILLER, alarm),
    293                     1000 * autoSnoozeMinutes * 60);
    294         }
    295     }
    296 
    297     private void disableKiller() {
    298         mHandler.removeMessages(KILLER);
    299     }
    300 
    301 
    302 }
    303