1 /*
2  * Copyright (C) 2014 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 
17 package com.android.bluetooth.mapclient;
18 
19 import android.annotation.Nullable;
20 import android.util.Log;
21 
22 import com.android.internal.annotations.VisibleForTesting;
23 
24 import org.json.JSONException;
25 import org.json.JSONObject;
26 import org.xmlpull.v1.XmlPullParser;
27 import org.xmlpull.v1.XmlPullParserException;
28 import org.xmlpull.v1.XmlPullParserFactory;
29 
30 import java.io.DataInputStream;
31 import java.io.IOException;
32 import java.math.BigInteger;
33 import java.util.HashMap;
34 
35 /**
36  * Object representation of event report received by MNS
37  *
38  * <p>This object will be received in {@link Client#EVENT_EVENT_REPORT} callback message.
39  */
40 public class EventReport {
41     private static final String TAG = "EventReport";
42     private final Type mType;
43     private final String mDateTime;
44     private final String mHandle;
45     private final String mFolder;
46     private final String mOldFolder;
47     private final Bmessage.Type mMsgType;
48 
49     @VisibleForTesting
EventReport(HashMap<String, String> attrs)50     EventReport(HashMap<String, String> attrs) throws IllegalArgumentException {
51         mType = parseType(attrs.get("type"));
52 
53         if (mType != Type.MEMORY_FULL && mType != Type.MEMORY_AVAILABLE) {
54             String handle = attrs.get("handle");
55             try {
56                 /* just to validate */
57                 new BigInteger(attrs.get("handle"), 16);
58 
59                 mHandle = attrs.get("handle");
60             } catch (NumberFormatException e) {
61                 throw new IllegalArgumentException("Invalid value for handle:" + handle);
62             }
63         } else {
64             mHandle = null;
65         }
66 
67         mFolder = attrs.get("folder");
68 
69         mOldFolder = attrs.get("old_folder");
70 
71         mDateTime = attrs.get("datetime");
72 
73         if (mType != Type.MEMORY_FULL && mType != Type.MEMORY_AVAILABLE) {
74             String s = attrs.get("msg_type");
75 
76             if (s != null && s.isEmpty()) {
77                 // Some phones (e.g. SGS3 for MessageDeleted) send empty
78                 // msg_type, in such case leave it as null rather than throw
79                 // parse exception
80                 mMsgType = null;
81             } else {
82                 mMsgType = parseMsgType(s);
83             }
84         } else {
85             mMsgType = null;
86         }
87     }
88 
fromStream(DataInputStream in)89     static EventReport fromStream(DataInputStream in) {
90         EventReport ev = null;
91 
92         try {
93             XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser();
94             xpp.setInput(in, "utf-8");
95 
96             int event = xpp.getEventType();
97             while (event != XmlPullParser.END_DOCUMENT) {
98                 switch (event) {
99                     case XmlPullParser.START_TAG:
100                         if (xpp.getName().equals("event")) {
101                             HashMap<String, String> attrs = new HashMap<String, String>();
102 
103                             for (int i = 0; i < xpp.getAttributeCount(); i++) {
104                                 attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i));
105                             }
106 
107                             ev = new EventReport(attrs);
108 
109                             // return immediately, only one event should be here
110                             return ev;
111                         }
112                         break;
113                 }
114 
115                 event = xpp.next();
116             }
117 
118         } catch (XmlPullParserException e) {
119             Log.e(TAG, "XML parser error when parsing XML", e);
120         } catch (IOException e) {
121             Log.e(TAG, "I/O error when parsing XML", e);
122         } catch (IllegalArgumentException e) {
123             Log.e(TAG, "Invalid event received", e);
124         }
125 
126         return ev;
127     }
128 
parseType(String type)129     private Type parseType(String type) throws IllegalArgumentException {
130         for (Type t : Type.values()) {
131             if (t.toString().equals(type)) {
132                 return t;
133             }
134         }
135 
136         throw new IllegalArgumentException("Invalid value for type: " + type);
137     }
138 
parseMsgType(String msgType)139     private Bmessage.Type parseMsgType(String msgType) throws IllegalArgumentException {
140         for (Bmessage.Type t : Bmessage.Type.values()) {
141             if (t.name().equals(msgType)) {
142                 return t;
143             }
144         }
145 
146         throw new IllegalArgumentException("Invalid value for msg_type: " + msgType);
147     }
148 
149     /**
150      * @return {@link EventReport.Type} object corresponding to <code>type</code> application
151      *     parameter in MAP specification
152      */
getType()153     public Type getType() {
154         return mType;
155     }
156 
157     /**
158      * @return value corresponding to <code>handle</code> parameter in MAP specification
159      */
getHandle()160     public String getHandle() {
161         return mHandle;
162     }
163 
164     /**
165      * @return value corresponding to <code>folder</code> parameter in MAP specification
166      */
getFolder()167     public String getFolder() {
168         return mFolder;
169     }
170 
171     /**
172      * @return value corresponding to <code>old_folder</code> parameter in MAP specification
173      */
getOldFolder()174     public String getOldFolder() {
175         return mOldFolder;
176     }
177 
178     /**
179      * @return {@link Bmessage.Type} object corresponding to <code>msg_type</code> application
180      *     parameter in MAP specification
181      */
getMsgType()182     public Bmessage.Type getMsgType() {
183         return mMsgType;
184     }
185 
186     /**
187      * @return value corresponding to <code>datetime</code> parameter in MAP specification for
188      *     NEW_MESSAGE (can be null)
189      */
190     @Nullable
getDateTime()191     public String getDateTime() {
192         return mDateTime;
193     }
194 
195     /**
196      * @return timestamp from the value corresponding to <code>datetime</code> parameter in MAP
197      *     specification for NEW_MESSAGE (can be null)
198      */
199     @Nullable
getTimestamp()200     public Long getTimestamp() {
201         if (mDateTime != null) {
202             ObexTime obexTime = new ObexTime(mDateTime);
203             if (obexTime != null) {
204                 return obexTime.getInstant().toEpochMilli();
205             }
206         }
207         return null;
208     }
209 
210     @Override
toString()211     public String toString() {
212         JSONObject json = new JSONObject();
213 
214         try {
215             json.put("type", mType);
216             if (mDateTime != null) {
217                 json.put("datetime", mDateTime);
218             }
219             json.put("handle", mHandle);
220             json.put("folder", mFolder);
221             json.put("old_folder", mOldFolder);
222             json.put("msg_type", mMsgType);
223         } catch (JSONException e) {
224             // do nothing
225         }
226 
227         return json.toString();
228     }
229 
230     public enum Type {
231         NEW_MESSAGE("NewMessage"),
232         DELIVERY_SUCCESS("DeliverySuccess"),
233         SENDING_SUCCESS("SendingSuccess"),
234         DELIVERY_FAILURE("DeliveryFailure"),
235         SENDING_FAILURE("SendingFailure"),
236         MEMORY_FULL("MemoryFull"),
237         MEMORY_AVAILABLE("MemoryAvailable"),
238         MESSAGE_DELETED("MessageDeleted"),
239         MESSAGE_SHIFT("MessageShift"),
240         READ_STATUS_CHANGED("ReadStatusChanged"),
241         MESSAGE_REMOVED("MessageRemoved"),
242         MESSAGE_EXTENDED_DATA_CHANGED("MessageExtendedDataChanged"),
243         PARTICIPANT_PRESENCE_CHANGED("ParticipantPresenceChanged"),
244         PARTICIPANT_CHAT_STATE_CHANGED("ParticipantChatStateChanged"),
245         CONVERSATION_CHANGED("ConversationChanged");
246         private final String mSpecName;
247 
Type(String specName)248         Type(String specName) {
249             mSpecName = specName;
250         }
251 
252         @Override
toString()253         public String toString() {
254             return mSpecName;
255         }
256     }
257 }
258