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 onBind(Intent intent)75 public IBinder onBind(Intent intent) { 76 mIsBound = true; 77 return mBinder; 78 } 79 80 @Override onUnbind(Intent intent)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 */ stopAlarm(Context context, AlarmInstance instance)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 startAlarm(AlarmInstance instance)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 stopCurrentAlarm()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 onCreate()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 onStartCommand(Intent intent, int flags, int startId)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 onDestroy()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