ddcd95571b1569fbb0893e4e2e600336bb3837af
[gc-dialer] / src / alarm_handler.py
1 #!/usr/bin/env python
2
3 import os
4 import time
5 import datetime
6 import ConfigParser
7 import logging
8
9 from PyQt4 import QtCore
10 import dbus
11
12
13 _FREMANTLE_ALARM = "Fremantle"
14 _DIABLO_ALARM = "Diablo"
15 _NO_ALARM = "None"
16
17
18 try:
19         import alarm
20         ALARM_TYPE = _FREMANTLE_ALARM
21 except (ImportError, OSError):
22         try:
23                 import osso.alarmd as alarmd
24                 ALARM_TYPE = _DIABLO_ALARM
25         except (ImportError, OSError):
26                 ALARM_TYPE = _NO_ALARM
27
28
29 _moduleLogger = logging.getLogger(__name__)
30
31
32 def _get_start_time(recurrence):
33         now = datetime.datetime.now()
34         startTimeMinute = now.minute + max(recurrence, 5) # being safe
35         startTimeHour = now.hour + int(startTimeMinute / 60)
36         startTimeMinute = startTimeMinute % 59
37         now.replace(minute=startTimeMinute)
38         timestamp = int(time.mktime(now.timetuple()))
39         return timestamp
40
41
42 def _create_recurrence_mask(recurrence, base):
43         """
44         >>> bin(_create_recurrence_mask(60, 60))
45         '0b1'
46         >>> bin(_create_recurrence_mask(30, 60))
47         '0b1000000000000000000000000000001'
48         >>> bin(_create_recurrence_mask(2, 60))
49         '0b10101010101010101010101010101010101010101010101010101010101'
50         >>> bin(_create_recurrence_mask(1, 60))
51         '0b111111111111111111111111111111111111111111111111111111111111'
52         """
53         mask = 0
54         for i in xrange(base / recurrence):
55                 mask |= 1 << (recurrence * i)
56         return mask
57
58
59 def _unpack_minutes(recurrence):
60         """
61         >>> _unpack_minutes(0)
62         (0, 0, 0)
63         >>> _unpack_minutes(1)
64         (0, 0, 1)
65         >>> _unpack_minutes(59)
66         (0, 0, 59)
67         >>> _unpack_minutes(60)
68         (0, 1, 0)
69         >>> _unpack_minutes(129)
70         (0, 2, 9)
71         >>> _unpack_minutes(5 * 60 * 24 + 3 * 60 + 2)
72         (5, 3, 2)
73         >>> _unpack_minutes(12 * 60 * 24 + 3 * 60 + 2)
74         (5, 3, 2)
75         """
76         minutesInAnHour = 60
77         minutesInDay = 24 * minutesInAnHour
78         minutesInAWeek = minutesInDay * 7
79
80         days = recurrence / minutesInDay
81         daysOfWeek = days % 7
82         recurrence -= days * minutesInDay
83         hours = recurrence / minutesInAnHour
84         recurrence -= hours * minutesInAnHour
85         mins = recurrence % minutesInAnHour
86         recurrence -= mins
87         assert recurrence == 0, "Recurrence %d" % recurrence
88         return daysOfWeek, hours, mins
89
90
91 class _FremantleAlarmHandler(object):
92
93         _INVALID_COOKIE = -1
94         _REPEAT_FOREVER = -1
95         _TITLE = "Dialcentral Notifications"
96         _LAUNCHER = os.path.abspath(os.path.join(os.path.dirname(__file__), "alarm_notify.py"))
97
98         def __init__(self):
99                 self._recurrence = 5
100
101                 self._alarmCookie = self._INVALID_COOKIE
102                 self._launcher = self._LAUNCHER
103
104         def load_settings(self, config, sectionName):
105                 try:
106                         self._recurrence = config.getint(sectionName, "recurrence")
107                         self._alarmCookie = config.getint(sectionName, "alarmCookie")
108                         launcher = config.get(sectionName, "notifier")
109                         if launcher:
110                                 self._launcher = launcher
111                 except ConfigParser.NoOptionError:
112                         pass
113                 except ConfigParser.NoSectionError:
114                         pass
115
116         def save_settings(self, config, sectionName):
117                 try:
118                         config.set(sectionName, "recurrence", str(self._recurrence))
119                         config.set(sectionName, "alarmCookie", str(self._alarmCookie))
120                         launcher = self._launcher if self._launcher != self._LAUNCHER else ""
121                         config.set(sectionName, "notifier", launcher)
122                 except ConfigParser.NoOptionError:
123                         pass
124                 except ConfigParser.NoSectionError:
125                         pass
126
127         def apply_settings(self, enabled, recurrence):
128                 if recurrence != self._recurrence or enabled != self.isEnabled:
129                         if self.isEnabled:
130                                 self._clear_alarm()
131                         if enabled:
132                                 self._set_alarm(recurrence)
133                 self._recurrence = int(recurrence)
134
135         @property
136         def recurrence(self):
137                 return self._recurrence
138
139         @property
140         def isEnabled(self):
141                 return self._alarmCookie != self._INVALID_COOKIE
142
143         def _set_alarm(self, recurrenceMins):
144                 assert 1 <= recurrenceMins, "Notifications set to occur too frequently: %d" % recurrenceMins
145                 alarmTime = _get_start_time(recurrenceMins)
146
147                 event = alarm.Event()
148                 event.appid = self._TITLE
149                 event.alarm_time = alarmTime
150                 event.recurrences_left = self._REPEAT_FOREVER
151
152                 action = event.add_actions(1)[0]
153                 action.flags |= alarm.ACTION_TYPE_EXEC | alarm.ACTION_WHEN_TRIGGERED
154                 action.command = self._launcher
155
156                 recurrence = event.add_recurrences(1)[0]
157                 recurrence.mask_min |= _create_recurrence_mask(recurrenceMins, 60)
158                 recurrence.mask_hour |= alarm.RECUR_HOUR_DONTCARE
159                 recurrence.mask_mday |= alarm.RECUR_MDAY_DONTCARE
160                 recurrence.mask_wday |= alarm.RECUR_WDAY_DONTCARE
161                 recurrence.mask_mon |= alarm.RECUR_MON_DONTCARE
162                 recurrence.special |= alarm.RECUR_SPECIAL_NONE
163
164                 assert event.is_sane()
165                 self._alarmCookie = alarm.add_event(event)
166
167         def _clear_alarm(self):
168                 if self._alarmCookie == self._INVALID_COOKIE:
169                         return
170                 alarm.delete_event(self._alarmCookie)
171                 self._alarmCookie = self._INVALID_COOKIE
172
173
174 class _DiabloAlarmHandler(object):
175
176         _INVALID_COOKIE = -1
177         _TITLE = "Dialcentral Notifications"
178         _LAUNCHER = os.path.abspath(os.path.join(os.path.dirname(__file__), "alarm_notify.py"))
179         _REPEAT_FOREVER = -1
180
181         def __init__(self):
182                 self._recurrence = 5
183
184                 bus = dbus.SystemBus()
185                 self._alarmdDBus = bus.get_object("com.nokia.alarmd", "/com/nokia/alarmd");
186                 self._alarmCookie = self._INVALID_COOKIE
187                 self._launcher = self._LAUNCHER
188
189         def load_settings(self, config, sectionName):
190                 try:
191                         self._recurrence = config.getint(sectionName, "recurrence")
192                         self._alarmCookie = config.getint(sectionName, "alarmCookie")
193                         launcher = config.get(sectionName, "notifier")
194                         if launcher:
195                                 self._launcher = launcher
196                 except ConfigParser.NoOptionError:
197                         pass
198                 except ConfigParser.NoSectionError:
199                         pass
200
201         def save_settings(self, config, sectionName):
202                 config.set(sectionName, "recurrence", str(self._recurrence))
203                 config.set(sectionName, "alarmCookie", str(self._alarmCookie))
204                 launcher = self._launcher if self._launcher != self._LAUNCHER else ""
205                 config.set(sectionName, "notifier", launcher)
206
207         def apply_settings(self, enabled, recurrence):
208                 if recurrence != self._recurrence or enabled != self.isEnabled:
209                         if self.isEnabled:
210                                 self._clear_alarm()
211                         if enabled:
212                                 self._set_alarm(recurrence)
213                 self._recurrence = int(recurrence)
214
215         @property
216         def recurrence(self):
217                 return self._recurrence
218
219         @property
220         def isEnabled(self):
221                 return self._alarmCookie != self._INVALID_COOKIE
222
223         def _set_alarm(self, recurrence):
224                 assert 1 <= recurrence, "Notifications set to occur too frequently: %d" % recurrence
225                 alarmTime = _get_start_time(recurrence)
226
227                 #Setup the alarm arguments so that they can be passed to the D-Bus add_event method
228                 _DEFAULT_FLAGS = (
229                         alarmd.ALARM_EVENT_NO_DIALOG |
230                         alarmd.ALARM_EVENT_NO_SNOOZE |
231                         alarmd.ALARM_EVENT_CONNECTED
232                 )
233                 action = []
234                 action.extend(['flags', _DEFAULT_FLAGS])
235                 action.extend(['title', self._TITLE])
236                 action.extend(['path', self._launcher])
237                 action.extend([
238                         'arguments',
239                         dbus.Array(
240                                 [alarmTime, int(27)],
241                                 signature=dbus.Signature('v')
242                         )
243                 ])  #int(27) used in place of alarm_index
244
245                 event = []
246                 event.extend([dbus.ObjectPath('/AlarmdEventRecurring'), dbus.UInt32(4)])
247                 event.extend(['action', dbus.ObjectPath('/AlarmdActionExec')])  #use AlarmdActionExec instead of AlarmdActionDbus
248                 event.append(dbus.UInt32(len(action) / 2))
249                 event.extend(action)
250                 event.extend(['time', dbus.Int64(alarmTime)])
251                 event.extend(['recurr_interval', dbus.UInt32(recurrence)])
252                 event.extend(['recurr_count', dbus.Int32(self._REPEAT_FOREVER)])
253
254                 self._alarmCookie = self._alarmdDBus.add_event(*event);
255
256         def _clear_alarm(self):
257                 if self._alarmCookie == self._INVALID_COOKIE:
258                         return
259                 deleteResult = self._alarmdDBus.del_event(dbus.Int32(self._alarmCookie))
260                 self._alarmCookie = self._INVALID_COOKIE
261                 assert deleteResult != -1, "Deleting of alarm event failed"
262
263
264 class _ApplicationAlarmHandler(object):
265
266         _REPEAT_FOREVER = -1
267         _MIN_TO_MS_FACTORY = 1000 * 60
268
269         def __init__(self):
270                 self._timer = QtCore.QTimer()
271                 self._timer.setSingleShot(False)
272                 self._timer.setInterval(5 * self._MIN_TO_MS_FACTORY)
273
274         def load_settings(self, config, sectionName):
275                 try:
276                         self._timer.setInterval(config.getint(sectionName, "recurrence") * self._MIN_TO_MS_FACTORY)
277                 except ConfigParser.NoOptionError:
278                         pass
279                 except ConfigParser.NoSectionError:
280                         pass
281                 self._timer.start()
282
283         def save_settings(self, config, sectionName):
284                 config.set(sectionName, "recurrence", str(self.recurrence))
285
286         def apply_settings(self, enabled, recurrence):
287                 self._timer.setInterval(recurrence * self._MIN_TO_MS_FACTORY)
288                 if enabled:
289                         self._timer.start()
290                 else:
291                         self._timer.stop()
292
293         @property
294         def notifySignal(self):
295                 return self._timer.timeout
296
297         @property
298         def recurrence(self):
299                 return int(self._timer.interval() / self._MIN_TO_MS_FACTORY)
300
301         @property
302         def isEnabled(self):
303                 return self._timer.isActive()
304
305
306 class _NoneAlarmHandler(object):
307
308         def __init__(self):
309                 self._enabled = False
310                 self._recurrence = 5
311
312         def load_settings(self, config, sectionName):
313                 try:
314                         self._recurrence = config.getint(sectionName, "recurrence")
315                         self._enabled = True
316                 except ConfigParser.NoOptionError:
317                         pass
318                 except ConfigParser.NoSectionError:
319                         pass
320
321         def save_settings(self, config, sectionName):
322                 config.set(sectionName, "recurrence", str(self.recurrence))
323
324         def apply_settings(self, enabled, recurrence):
325                 self._enabled = enabled
326
327         @property
328         def recurrence(self):
329                 return self._recurrence
330
331         @property
332         def isEnabled(self):
333                 return self._enabled
334
335
336 _BACKGROUND_ALARM_FACTORY = {
337         _FREMANTLE_ALARM: _FremantleAlarmHandler,
338         _DIABLO_ALARM: _DiabloAlarmHandler,
339         _NO_ALARM: None,
340 }[ALARM_TYPE]
341
342
343 class AlarmHandler(object):
344
345         ALARM_NONE = "No Alert"
346         ALARM_BACKGROUND = "Background Alert"
347         ALARM_APPLICATION = "Application Alert"
348         ALARM_TYPES = [ALARM_NONE, ALARM_BACKGROUND, ALARM_APPLICATION]
349
350         ALARM_FACTORY = {
351                 ALARM_NONE: _NoneAlarmHandler,
352                 ALARM_BACKGROUND: _BACKGROUND_ALARM_FACTORY,
353                 ALARM_APPLICATION: _ApplicationAlarmHandler,
354         }
355
356         def __init__(self):
357                 self._alarms = {self.ALARM_NONE: _NoneAlarmHandler()}
358                 self._currentAlarmType = self.ALARM_NONE
359
360         def load_settings(self, config, sectionName):
361                 try:
362                         self._currentAlarmType = config.get(sectionName, "alarm")
363                 except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
364                         _moduleLogger.exception("Falling back to old style")
365                         self._currentAlarmType = self.ALARM_BACKGROUND
366                 if self._currentAlarmType not in self.ALARM_TYPES:
367                         self._currentAlarmType = self.ALARM_NONE
368
369                 self._init_alarm(self._currentAlarmType)
370                 if self._currentAlarmType in self._alarms:
371                         self._alarms[self._currentAlarmType].load_settings(config, sectionName)
372                         if not self._alarms[self._currentAlarmType].isEnabled:
373                                 _moduleLogger.info("Config file lied, not actually enabled")
374                                 self._currentAlarmType = self.ALARM_NONE
375                 else:
376                         _moduleLogger.info("Background alerts not supported")
377                         self._currentAlarmType = self.ALARM_NONE
378
379         def save_settings(self, config, sectionName):
380                 config.set(sectionName, "alarm", self._currentAlarmType)
381                 self._alarms[self._currentAlarmType].save_settings(config, sectionName)
382
383         def apply_settings(self, t, recurrence):
384                 self._init_alarm(t)
385                 newHandler = self._alarms[t]
386                 oldHandler = self._alarms[self._currentAlarmType]
387                 if newHandler != oldHandler:
388                         oldHandler.apply_settings(False, 0)
389                 newHandler.apply_settings(True, recurrence)
390                 self._currentAlarmType = t
391
392         @property
393         def alarmType(self):
394                 return self._currentAlarmType
395
396         @property
397         def backgroundNotificationsSupported(self):
398                 return self.ALARM_FACTORY[self.ALARM_BACKGROUND] is not None
399
400         @property
401         def applicationNotifySignal(self):
402                 self._init_alarm(self.ALARM_APPLICATION)
403                 return self._alarms[self.ALARM_APPLICATION].notifySignal
404
405         @property
406         def recurrence(self):
407                 return self._alarms[self._currentAlarmType].recurrence
408
409         @property
410         def isEnabled(self):
411                 return self._currentAlarmType != self.ALARM_NONE
412
413         def _init_alarm(self, t):
414                 if t not in self._alarms and self.ALARM_FACTORY[t] is not None:
415                         self._alarms[t] = self.ALARM_FACTORY[t]()
416
417
418 def main():
419         logFormat = '(%(relativeCreated)5d) %(levelname)-5s %(threadName)s.%(name)s.%(funcName)s: %(message)s'
420         logging.basicConfig(level=logging.DEBUG, format=logFormat)
421         import constants
422         try:
423                 import optparse
424         except ImportError:
425                 return
426
427         parser = optparse.OptionParser()
428         parser.add_option("-x", "--display", action="store_true", dest="display", help="Display data")
429         parser.add_option("-e", "--enable", action="store_true", dest="enabled", help="Whether the alarm should be enabled or not", default=False)
430         parser.add_option("-d", "--disable", action="store_false", dest="enabled", help="Whether the alarm should be enabled or not", default=False)
431         parser.add_option("-r", "--recurrence", action="store", type="int", dest="recurrence", help="How often the alarm occurs", default=5)
432         (commandOptions, commandArgs) = parser.parse_args()
433
434         alarmHandler = AlarmHandler()
435         config = ConfigParser.SafeConfigParser()
436         config.read(constants._user_settings_)
437         alarmHandler.load_settings(config, "alarm")
438
439         if commandOptions.display:
440                 print "Alarm (%s) is %s for every %d minutes" % (
441                         alarmHandler._alarmCookie,
442                         "enabled" if alarmHandler.isEnabled else "disabled",
443                         alarmHandler.recurrence,
444                 )
445         else:
446                 isEnabled = commandOptions.enabled
447                 recurrence = commandOptions.recurrence
448                 alarmHandler.apply_settings(isEnabled, recurrence)
449
450                 alarmHandler.save_settings(config, "alarm")
451                 configFile = open(constants._user_settings_, "wb")
452                 try:
453                         config.write(configFile)
454                 finally:
455                         configFile.close()
456
457
458 if __name__ == "__main__":
459         main()