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