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