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 */
16package com.android.deskclock.alarms;
17
18import android.app.Service;
19import android.content.ContentResolver;
20import android.content.Context;
21import android.content.Intent;
22import android.os.IBinder;
23import android.telephony.PhoneStateListener;
24import android.telephony.TelephonyManager;
25
26import com.android.deskclock.AlarmAlertWakeLock;
27import com.android.deskclock.Log;
28import com.android.deskclock.provider.AlarmInstance;
29
30/**
31 * This service is in charge of starting/stoping the alarm. It will bring up and manage the
32 * {@link AlarmActivity} as well as {@link AlarmKlaxon}.
33 */
34public class AlarmService extends Service {
35    // A public action send by AlarmService when the alarm has started.
36    public static final String ALARM_ALERT_ACTION = "com.android.deskclock.ALARM_ALERT";
37
38    // A public action sent by AlarmService when the alarm has stopped for any reason.
39    public static final String ALARM_DONE_ACTION = "com.android.deskclock.ALARM_DONE";
40
41    // Private action used to start an alarm with this service.
42    public static final String START_ALARM_ACTION = "START_ALARM";
43
44    // Private action used to stop an alarm with this service.
45    public static final String STOP_ALARM_ACTION = "STOP_ALARM";
46
47    /**
48     * Utility method to help start alarm properly. If alarm is already firing, it
49     * will mark it as missed and start the new one.
50     *
51     * @param context application context
52     * @param instance to trigger alarm
53     */
54    public static void startAlarm(Context context, AlarmInstance instance) {
55        Intent intent = AlarmInstance.createIntent(context, AlarmService.class, instance.mId);
56        intent.setAction(START_ALARM_ACTION);
57
58        // Maintain a cpu wake lock until the service can get it
59        AlarmAlertWakeLock.acquireCpuWakeLock(context);
60        context.startService(intent);
61    }
62
63    /**
64     * Utility method to help stop an alarm properly. Nothing will happen, if alarm is not firing
65     * or using a different instance.
66     *
67     * @param context application context
68     * @param instance you are trying to stop
69     */
70    public static void stopAlarm(Context context, AlarmInstance instance) {
71        Intent intent = AlarmInstance.createIntent(context, AlarmService.class, instance.mId);
72        intent.setAction(STOP_ALARM_ACTION);
73
74        // We don't need a wake lock here, since we are trying to kill an alarm
75        context.startService(intent);
76    }
77
78    private TelephonyManager mTelephonyManager;
79    private int mInitialCallState;
80    private AlarmInstance mCurrentAlarm = null;
81
82    private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
83        @Override
84        public void onCallStateChanged(int state, String ignored) {
85            // The user might already be in a call when the alarm fires. When
86            // we register onCallStateChanged, we get the initial in-call state
87            // which kills the alarm. Check against the initial call state so
88            // we don't kill the alarm during a call.
89            if (state != TelephonyManager.CALL_STATE_IDLE && state != mInitialCallState) {
90                sendBroadcast(AlarmStateManager.createStateChangeIntent(AlarmService.this,
91                        "AlarmService", mCurrentAlarm, AlarmInstance.MISSED_STATE));
92            }
93        }
94    };
95
96    private void startAlarm(AlarmInstance instance) {
97        Log.v("AlarmService.start with instance: " + instance.mId);
98        if (mCurrentAlarm != null) {
99            AlarmStateManager.setMissedState(this, mCurrentAlarm);
100            stopCurrentAlarm();
101        }
102
103        AlarmAlertWakeLock.acquireCpuWakeLock(this);
104        mCurrentAlarm = instance;
105        AlarmNotifications.showAlarmNotification(this, mCurrentAlarm);
106        mInitialCallState = mTelephonyManager.getCallState();
107        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
108        boolean inCall = mInitialCallState != TelephonyManager.CALL_STATE_IDLE;
109        AlarmKlaxon.start(this, mCurrentAlarm, inCall);
110        sendBroadcast(new Intent(ALARM_ALERT_ACTION));
111    }
112
113    private void stopCurrentAlarm() {
114        if (mCurrentAlarm == null) {
115            Log.v("There is no current alarm to stop");
116            return;
117        }
118
119        Log.v("AlarmService.stop with instance: " + mCurrentAlarm.mId);
120        AlarmKlaxon.stop(this);
121        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
122        sendBroadcast(new Intent(ALARM_DONE_ACTION));
123        mCurrentAlarm = null;
124        AlarmAlertWakeLock.releaseCpuLock();
125    }
126
127    @Override
128    public void onCreate() {
129        super.onCreate();
130        mTelephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
131    }
132
133    @Override
134    public int onStartCommand(Intent intent, int flags, int startId) {
135        Log.v("AlarmService.onStartCommand() with intent: " + intent.toString());
136
137        long instanceId = AlarmInstance.getId(intent.getData());
138        if (START_ALARM_ACTION.equals(intent.getAction())) {
139            ContentResolver cr = this.getContentResolver();
140            AlarmInstance instance = AlarmInstance.getInstance(cr, instanceId);
141            if (instance == null) {
142                Log.e("No instance found to start alarm: " + instanceId);
143                if (mCurrentAlarm != null) {
144                    // Only release lock if we are not firing alarm
145                    AlarmAlertWakeLock.releaseCpuLock();
146                }
147                return Service.START_NOT_STICKY;
148            } else if (mCurrentAlarm != null && mCurrentAlarm.mId == instanceId) {
149                Log.e("Alarm already started for instance: " + instanceId);
150                return Service.START_NOT_STICKY;
151            }
152            startAlarm(instance);
153        } else if(STOP_ALARM_ACTION.equals(intent.getAction())) {
154            if (mCurrentAlarm != null && mCurrentAlarm.mId != instanceId) {
155                Log.e("Can't stop alarm for instance: " + instanceId +
156                    " because current alarm is: " + mCurrentAlarm.mId);
157                return Service.START_NOT_STICKY;
158            }
159            stopSelf();
160        }
161
162        return Service.START_NOT_STICKY;
163    }
164
165    @Override
166    public void onDestroy() {
167        Log.v("AlarmService.onDestroy() called");
168        super.onDestroy();
169        stopCurrentAlarm();
170    }
171
172    @Override
173    public IBinder onBind(Intent intent) {
174        return null;
175    }
176}
177