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 start an alarm with this service. */ 63 public static final String START_ALARM_ACTION = "START_ALARM"; 64 65 /** Private action used to stop an alarm with this service. */ 66 public static final String STOP_ALARM_ACTION = "STOP_ALARM"; 67 68 /** Binder given to AlarmActivity */ 69 private final IBinder mBinder = new Binder(); 70 71 /** Whether the service is currently bound to AlarmActivity */ 72 private boolean mIsBound = false; 73 74 /** Whether the receiver is currently registered */ 75 private boolean mIsRegistered = false; 76 77 @Override 78 public IBinder onBind(Intent intent) { 79 mIsBound = true; 80 return mBinder; 81 } 82 83 @Override 84 public boolean onUnbind(Intent intent) { 85 mIsBound = false; 86 return super.onUnbind(intent); 87 } 88 89 /** 90 * Utility method to help start alarm properly. If alarm is already firing, it 91 * will mark it as missed and start the new one. 92 * 93 * @param context application context 94 * @param instance to trigger alarm 95 */ 96 public static void startAlarm(Context context, AlarmInstance instance) { 97 final Intent intent = AlarmInstance.createIntent(context, AlarmService.class, instance.mId) 98 .setAction(START_ALARM_ACTION); 99 100 // Maintain a cpu wake lock until the service can get it 101 AlarmAlertWakeLock.acquireCpuWakeLock(context); 102 context.startService(intent); 103 } 104 105 /** 106 * Utility method to help stop an alarm properly. Nothing will happen, if alarm is not firing 107 * or using a different instance. 108 * 109 * @param context application context 110 * @param instance you are trying to stop 111 */ 112 public static void stopAlarm(Context context, AlarmInstance instance) { 113 final Intent intent = AlarmInstance.createIntent(context, AlarmService.class, instance.mId) 114 .setAction(STOP_ALARM_ACTION); 115 116 // We don't need a wake lock here, since we are trying to kill an alarm 117 context.startService(intent); 118 } 119 120 private TelephonyManager mTelephonyManager; 121 private int mInitialCallState; 122 private AlarmInstance mCurrentAlarm = null; 123 124 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 125 @Override 126 public void onCallStateChanged(int state, String ignored) { 127 // The user might already be in a call when the alarm fires. When 128 // we register onCallStateChanged, we get the initial in-call state 129 // which kills the alarm. Check against the initial call state so 130 // we don't kill the alarm during a call. 131 if (state != TelephonyManager.CALL_STATE_IDLE && state != mInitialCallState) { 132 sendBroadcast(AlarmStateManager.createStateChangeIntent(AlarmService.this, 133 "AlarmService", mCurrentAlarm, AlarmInstance.MISSED_STATE)); 134 } 135 } 136 }; 137 138 private void startAlarm(AlarmInstance instance) { 139 LogUtils.v("AlarmService.start with instance: " + instance.mId); 140 if (mCurrentAlarm != null) { 141 AlarmStateManager.setMissedState(this, mCurrentAlarm); 142 stopCurrentAlarm(); 143 } 144 145 AlarmAlertWakeLock.acquireCpuWakeLock(this); 146 147 Events.sendEvent(R.string.category_alarm, R.string.action_fire, 0); 148 149 mCurrentAlarm = instance; 150 AlarmNotifications.showAlarmNotification(this, mCurrentAlarm); 151 mInitialCallState = mTelephonyManager.getCallState(); 152 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); 153 AlarmKlaxon.start(this, mCurrentAlarm); 154 sendBroadcast(new Intent(ALARM_ALERT_ACTION)); 155 } 156 157 private void stopCurrentAlarm() { 158 if (mCurrentAlarm == null) { 159 LogUtils.v("There is no current alarm to stop"); 160 return; 161 } 162 163 LogUtils.v("AlarmService.stop with instance: %s", (Object) mCurrentAlarm.mId); 164 AlarmKlaxon.stop(this); 165 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); 166 sendBroadcast(new Intent(ALARM_DONE_ACTION)); 167 168 mCurrentAlarm = null; 169 AlarmAlertWakeLock.releaseCpuLock(); 170 } 171 172 private final BroadcastReceiver mActionsReceiver = new BroadcastReceiver() { 173 @Override 174 public void onReceive(Context context, Intent intent) { 175 final String action = intent.getAction(); 176 LogUtils.i("AlarmService received intent %s", action); 177 if (mCurrentAlarm == null || mCurrentAlarm.mAlarmState != AlarmInstance.FIRED_STATE) { 178 LogUtils.i("No valid firing alarm"); 179 return; 180 } 181 182 if (mIsBound) { 183 LogUtils.i("AlarmActivity bound; AlarmService no-op"); 184 return; 185 } 186 187 switch (action) { 188 case ALARM_SNOOZE_ACTION: 189 // Set the alarm state to snoozed. 190 // If this broadcast receiver is handling the snooze intent then AlarmActivity 191 // must not be showing, so always show snooze toast. 192 AlarmStateManager.setSnoozeState(context, mCurrentAlarm, true /* showToast */); 193 Events.sendAlarmEvent(R.string.action_snooze, R.string.label_intent); 194 break; 195 case ALARM_DISMISS_ACTION: 196 // Set the alarm state to dismissed. 197 AlarmStateManager.setDismissState(context, mCurrentAlarm); 198 Events.sendAlarmEvent(R.string.action_dismiss, R.string.label_intent); 199 break; 200 } 201 } 202 }; 203 204 @Override 205 public void onCreate() { 206 super.onCreate(); 207 mTelephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); 208 209 // Register the broadcast receiver 210 final IntentFilter filter = new IntentFilter(ALARM_SNOOZE_ACTION); 211 filter.addAction(ALARM_DISMISS_ACTION); 212 registerReceiver(mActionsReceiver, filter); 213 mIsRegistered = true; 214 } 215 216 @Override 217 public int onStartCommand(Intent intent, int flags, int startId) { 218 LogUtils.v("AlarmService.onStartCommand() with %s", intent); 219 220 final long instanceId = AlarmInstance.getId(intent.getData()); 221 switch (intent.getAction()) { 222 case START_ALARM_ACTION: 223 final ContentResolver cr = this.getContentResolver(); 224 final AlarmInstance instance = AlarmInstance.getInstance(cr, instanceId); 225 if (instance == null) { 226 LogUtils.e("No instance found to start alarm: %d", instanceId); 227 if (mCurrentAlarm != null) { 228 // Only release lock if we are not firing alarm 229 AlarmAlertWakeLock.releaseCpuLock(); 230 } 231 break; 232 } 233 234 if (mCurrentAlarm != null && mCurrentAlarm.mId == instanceId) { 235 LogUtils.e("Alarm already started for instance: %d", instanceId); 236 break; 237 } 238 startAlarm(instance); 239 break; 240 case STOP_ALARM_ACTION: 241 if (mCurrentAlarm != null && mCurrentAlarm.mId != instanceId) { 242 LogUtils.e("Can't stop alarm for instance: %d because current alarm is: %d", 243 instanceId, mCurrentAlarm.mId); 244 break; 245 } 246 stopCurrentAlarm(); 247 stopSelf(); 248 } 249 250 return Service.START_NOT_STICKY; 251 } 252 253 @Override 254 public void onDestroy() { 255 LogUtils.v("AlarmService.onDestroy() called"); 256 super.onDestroy(); 257 if (mCurrentAlarm != null) { 258 stopCurrentAlarm(); 259 } 260 261 if (mIsRegistered) { 262 unregisterReceiver(mActionsReceiver); 263 mIsRegistered = false; 264 } 265 } 266 } 267