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