1# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import datetime
6import dbus
7import dbus.types
8import logging
9
10import sms
11
12from autotest_lib.client.cros.cellular import mm1_constants
13
14class SmsHandlerException(Exception):
15    """ Exception class for errors raised by SmsHandler. """
16    pass
17
18
19class SmsHandler(object):
20    """
21    Handles all SMS operations, which includes storing received SMS messages,
22    as well as notifying the modem when a new SMS is received.
23
24    """
25
26    # TODO(armansito): Apply a character limit to SMS messages for multi-part
27    # delivery. This constant here is defined for future reference but is
28    # currently unusued. The value that is currently assigned to it is
29    # arbitrary and a more meaningful default value should be used, though it
30    # really doesn't matter from a testing perspective.
31    SMS_CHAR_LIMIT = 200
32
33    def __init__(self, modem, bus=None):
34        self._modem = modem
35        self._messages = {}  # Mapping from DBus Object paths to sms.SMS.
36        self._bus = bus
37
38
39    @property
40    def bus(self):
41        """
42        Returns the current bus assigned to this object. This is the bus
43        on which new SMS objects will be created.
44
45        @returns: An instance of dbus.Bus.
46
47        """
48        return self._bus
49
50
51    @bus.setter
52    def bus(self, bus):
53        """
54        Sets the current bus on which SMS objects should be created.
55
56        @param bus: An instance of dbus.Bus.
57
58        """
59        self._bus = bus
60
61
62    @classmethod
63    def set_char_limit(cls, limit):
64        cls.SMS_CHAR_LIMIT = limit
65
66
67    def clear_messages(self):
68        """ Clears all SMS messages. """
69        self._messages.clear()
70
71
72    def delete_message(self, path):
73        """
74        Removes the message with DBus object path |path|. This operation
75        has no effect if and SMS object with path |path| is unknown.
76
77        @param path: DBus object path of the SMS object to remove.
78
79        """
80        try:
81            self._messages.pop(path)
82        except KeyError:
83            logging.info('SMS object with path "%s" not found.', path)
84            pass
85
86
87    def list_messages(self):
88        """
89        Returns a list of DBus object paths belonging to stored SMS messages.
90
91        """
92        return self._messages.keys()
93
94
95    def get_message_with_path(self, path):
96        """
97        Returns the SMS message with the DBus object path that matches |path|.
98
99        @param path: DBus object path of the requested SMS object.
100        @returns: An instance of sms.SMS or None, if an SMS object with the
101                requested path is not found.
102
103        """
104        sms_object = self._messages.get(path, None)
105        if sms_object:
106            assert sms_object.path == path
107        return sms_object
108
109
110    def construct_sms(self, text, sender):
111        """
112        Constructs an SMS object and stores it internally. SMS messages should
113        be created using this method instead of being instantiated directly.
114        Once an SMS is created, it can be obtained via get_message_with_path.
115
116        @param text: The message contents, in UTF-8 format.
117        @param sender: The phone number of the sender.
118        @returns: An instance of sms.SMS.
119
120        """
121        if self._bus is None:
122            raise SmsHandlerException('A bus has to be set before SMS objects '
123                                      'can be created.')
124        sms_object = sms.SMS(self._bus, sender, text)
125        self._messages[sms_object.path] = sms_object
126        # TODO(armansito): Split SMSs that are too big into multiple chunks.
127        return sms_object
128
129
130    def send_sms(self, text, sender):
131        """
132        Queues up an SMS to be sent and simulates SMS delivery state updates.
133
134        @param text: The message contents, in UTF-8 format.
135        @param sender: The phone number of the sender.
136
137        """
138        # TODO(armansito): Support this if it's ever needed (unlikely).
139        raise SmsHandlerException('Sending SMSs is not supported.')
140
141
142    def receive_sms(self, text, sender, is_status_report=False):
143        """
144        Simulates a received SMS message.
145
146        @param text: The message contents, in UTF-8 format.
147        @param sender: The phone number of the sender.
148        @param is_status_report: If True, the SMS will be formatted as a status
149                report.
150
151        """
152        sms_object = self.construct_sms(text, sender)
153
154        # Use the current time for both DischargeTimestamp and Timestamp. Our
155        # SMS messages travel faster than the speed of light.
156        timestamp = datetime.datetime.isoformat(datetime.datetime.now())
157        sms_object.Set(mm1_constants.I_SMS, 'Timestamp', timestamp)
158        sms_object.Set(mm1_constants.I_SMS, 'DischargeTimestamp', timestamp)
159
160        # Receive messages right away.
161        sms_object.Set(mm1_constants.I_SMS, 'State',
162                       mm1_constants.MM_SMS_STATE_RECEIVED)
163        sms_object.Set(mm1_constants.I_SMS, 'PduType',
164                       mm1_constants.MM_SMS_PDU_TYPE_DELIVER)
165
166        # Emit an Added message.
167        self._modem.Added(dbus.types.ObjectPath(sms_object.path), True)
168