Home | History | Annotate | Download | only in alarms
      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 package com.android.deskclock.alarms;
     17 
     18 import android.app.Service;
     19 import android.content.BroadcastReceiver;
     20 import android.content.ContentResolver;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.IntentFilter;
     24 import android.os.Binder;
     25 import android.os.IBinder;
     26 import android.telephony.PhoneStateListener;
     27 import android.telephony.TelephonyManager;
     28 
     29 import com.android.deskclock.AlarmAlertWakeLock;
     30 import com.android.deskclock.LogUtils;
     31 import com.android.deskclock.R;
     32 import com.android.deskclock.events.Events;
     33 import com.android.deskclock.provider.AlarmInstance;
     34 
     35 /**
     36  * This service is in charge of starting/stopping the alarm. It will bring up and manage the
     37  * {@link AlarmActivity} as well as {@link AlarmKlaxon}.
     38  *
     39  * Registers a broadcast receiver to listen for snooze/dismiss intents. The broadcast receiver
     40  * exits early if AlarmActivity is bound to prevent double-processing of the snooze/dismiss intents.
     41  */
     42 public class AlarmService extends Service {
     43     /**
     44      * AlarmActivity and AlarmService (when unbound) listen for this broadcast intent
     45      * so that other applications can snooze the alarm (after ALARM_ALERT_ACTION and before
     46      * ALARM_DONE_ACTION).
     47      */
     48     public static final String ALARM_SNOOZE_ACTION = "com.android.deskclock.ALARM_SNOOZE";
     49 
     50     /**
     51      * AlarmActivity and AlarmService listen for this broadcast intent so that other
     52      * applications can dismiss the alarm (after ALARM_ALERT_ACTION and before ALARM_DONE_ACTION).
     53      */
     54     public static final String ALARM_DISMISS_ACTION = "com.android.deskclock.ALARM_DISMISS";
     55 
     56     /** A public action sent by AlarmService when the alarm has started. */
     57     public static final String ALARM_ALERT_ACTION = "com.android.deskclock.ALARM_ALERT";
     58 
     59     /** A public action sent by AlarmService when the alarm has stopped for any reason. */
     60     public static final String ALARM_DONE_ACTION = "com.android.deskclock.ALARM_DONE";
     61 
     62     /** Private action used to stop an alarm with this service. */
     63     public static final String STOP_ALARM_ACTION = "STOP_ALARM";
     64 
     65     /** Binder given to AlarmActivity */
     66     private final IBinder mBinder = new Binder();
     67 
     68     /** Whether the service is currently bound to AlarmActivity */
     69     private boolean mIsBound = false;
     70 
     71     /** Whether the receiver is currently registered */
     72     private boolean mIsRegistered = false;
     73 
     74     @Override
     75     public IBinder onBind(Intent intent) {
     76         mIsBound = true;
     77         return mBinder;
     78     }
     79 
     80     @Override
     81     public boolean onUnbind(Intent intent) {
     82         mIsBound = false;
     83         return super.onUnbind(intent);
     84     }
     85 
     86     /**
     87      * Utility method to help stop an alarm properly. Nothing will happen, if alarm is not firing
     88      * or using a different instance.
     89      *
     90      * @param context application context
     91      * @param instance you are trying to stop
     92      */
     93     public static void stopAlarm(Context context, AlarmInstance instance) {
     94         final Intent intent = AlarmInstance.createIntent(context, AlarmService.class, instance.mId)
     95                 .setAction(STOP_ALARM_ACTION);
     96 
     97         // We don't need a wake lock here, since we are trying to kill an alarm
     98         context.startService(intent);
     99     }
    100 
    101     private TelephonyManager mTelephonyManager;
    102     private int mInitialCallState;
    103     private AlarmInstance mCurrentAlarm = null;
    104 
    105     private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
    106         @Override
    107         public void onCallStateChanged(int state, String ignored) {
    108             // The user might already be in a call when the alarm fires. When
    109             // we register onCallStateChanged, we get the initial in-call state
    110             // which kills the alarm. Check against the initial call state so
    111             // we don't kill the alarm during a call.
    112             if (state != TelephonyManager.CALL_STATE_IDLE && state != mInitialCallState) {
    113                 startService(AlarmStateManager.createStateChangeIntent(AlarmService.this,
    114                         "AlarmService", mCurrentAlarm, AlarmInstance.MISSED_STATE));
    115             }
    116         }
    117     };
    118 
    119     private void startAlarm(AlarmInstance instance) {
    120         LogUtils.v("AlarmService.start with instance: " + instance.mId);
    121         if (mCurrentAlarm != null) {
    122             AlarmStateManager.setMissedState(this, mCurrentAlarm);
    123             stopCurrentAlarm();
    124         }
    125 
    126         AlarmAlertWakeLock.acquireCpuWakeLock(this);
    127 
    128         mCurrentAlarm = instance;
    129         AlarmNotifications.showAlarmNotification(this, mCurrentAlarm);
    130         mInitialCallState = mTelephonyManager.getCallState();
    131         mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
    132         AlarmKlaxon.start(this, mCurrentAlarm);
    133         sendBroadcast(new Intent(ALARM_ALERT_ACTION));
    134     }
    135 
    136     private void stopCurrentAlarm() {
    137         if (mCurrentAlarm == null) {
    138             LogUtils.v("There is no current alarm to stop");
    139             return;
    140         }
    141 
    142         final long instanceId = mCurrentAlarm.mId;
    143         LogUtils.v("AlarmService.stop with instance: %s", instanceId);
    144 
    145         AlarmKlaxon.stop(this);
    146         mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
    147         sendBroadcast(new Intent(ALARM_DONE_ACTION));
    148 
    149         // Since we use the same id for all notifications, the system has no way to distinguish the
    150         // firing notification we were bound to from other subsequent notifications posted for the
    151         // same AlarmInstance (e.g. after snoozing). We workaround the issue by forcing removal of
    152         // the notification and re-posting it.
    153         stopForeground(true /* removeNotification */);
    154         mCurrentAlarm = AlarmInstance.getInstance(getContentResolver(), instanceId);
    155         if (mCurrentAlarm != null) {
    156             AlarmNotifications.updateNotification(this, mCurrentAlarm);
    157         }
    158 
    159         mCurrentAlarm = null;
    160         AlarmAlertWakeLock.releaseCpuLock();
    161     }
    162 
    163     private final BroadcastReceiver mActionsReceiver = new BroadcastReceiver() {
    164         @Override
    165         public void onReceive(Context context, Intent intent) {
    166             final String action = intent.getAction();
    167             LogUtils.i("AlarmService received intent %s", action);
    168             if (mCurrentAlarm == null || mCurrentAlarm.mAlarmState != AlarmInstance.FIRED_STATE) {
    169                 LogUtils.i("No valid firing alarm");
    170                 return;
    171             }
    172 
    173             if (mIsBound) {
    174                 LogUtils.i("AlarmActivity bound; AlarmService no-op");
    175                 return;
    176             }
    177 
    178             switch (action) {
    179                 case ALARM_SNOOZE_ACTION:
    180                     // Set the alarm state to snoozed.
    181                     // If this broadcast receiver is handling the snooze intent then AlarmActivity
    182                     // must not be showing, so always show snooze toast.
    183                     AlarmStateManager.setSnoozeState(context, mCurrentAlarm, true /* showToast */);
    184                     Events.sendAlarmEvent(R.string.action_snooze, R.string.label_intent);
    185                     break;
    186                 case ALARM_DISMISS_ACTION:
    187                     // Set the alarm state to dismissed.
    188                     AlarmStateManager.deleteInstanceAndUpdateParent(context, mCurrentAlarm);
    189                     Events.sendAlarmEvent(R.string.action_dismiss, R.string.label_intent);
    190                     break;
    191             }
    192         }
    193     };
    194 
    195     @Override
    196     public void onCreate() {
    197         super.onCreate();
    198         mTelephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
    199 
    200         // Register the broadcast receiver
    201         final IntentFilter filter = new IntentFilter(ALARM_SNOOZE_ACTION);
    202         filter.addAction(ALARM_DISMISS_ACTION);
    203         registerReceiver(mActionsReceiver, filter);
    204         mIsRegistered = true;
    205     }
    206 
    207     @Override
    208     public int onStartCommand(Intent intent, int flags, int startId) {
    209         LogUtils.v("AlarmService.onStartCommand() with %s", intent);
    210 
    211         final long instanceId = AlarmInstance.getId(intent.getData());
    212         switch (intent.getAction()) {
    213             case AlarmStateManager.CHANGE_STATE_ACTION:
    214                 AlarmStateManager.handleIntent(this, intent);
    215 
    216                 // If state is changed to firing, actually fire the alarm!
    217                 final int alarmState = intent.getIntExtra(AlarmStateManager.ALARM_STATE_EXTRA, -1);
    218                 if (alarmState == AlarmInstance.FIRED_STATE) {
    219                     final ContentResolver cr = this.getContentResolver();
    220                     final AlarmInstance instance = AlarmInstance.getInstance(cr, instanceId);
    221                     if (instance == null) {
    222                         LogUtils.e("No instance found to start alarm: %d", instanceId);
    223                         if (mCurrentAlarm != null) {
    224                             // Only release lock if we are not firing alarm
    225                             AlarmAlertWakeLock.releaseCpuLock();
    226                         }
    227                         break;
    228                     }
    229 
    230                     if (mCurrentAlarm != null && mCurrentAlarm.mId == instanceId) {
    231                         LogUtils.e("Alarm already started for instance: %d", instanceId);
    232                         break;
    233                     }
    234                     startAlarm(instance);
    235                 }
    236                 break;
    237             case STOP_ALARM_ACTION:
    238                 if (mCurrentAlarm != null && mCurrentAlarm.mId != instanceId) {
    239                     LogUtils.e("Can't stop alarm for instance: %d because current alarm is: %d",
    240                             instanceId, mCurrentAlarm.mId);
    241                     break;
    242                 }
    243                 stopCurrentAlarm();
    244                 stopSelf();
    245         }
    246 
    247         return Service.START_NOT_STICKY;
    248     }
    249 
    250     @Override
    251     public void onDestroy() {
    252         LogUtils.v("AlarmService.onDestroy() called");
    253         super.onDestroy();
    254         if (mCurrentAlarm != null) {
    255             stopCurrentAlarm();
    256         }
    257 
    258         if (mIsRegistered) {
    259             unregisterReceiver(mActionsReceiver);
    260             mIsRegistered = false;
    261         }
    262     }
    263 }
    264