BluetoothMapContentObserver.java revision ed70219e41ba68196798dcbf75b782d13fb88603
1/*
2 * Copyright (C) 2014 Samsung System LSI
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *      http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15package com.android.bluetooth.map;
16
17import android.annotation.TargetApi;
18import android.app.Activity;
19import android.app.PendingIntent;
20import android.content.BroadcastReceiver;
21import android.content.ContentProviderClient;
22import android.content.ContentResolver;
23import android.content.ContentUris;
24import android.content.ContentValues;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.IntentFilter.MalformedMimeTypeException;
29import android.database.ContentObserver;
30import android.database.Cursor;
31import android.net.Uri;
32import android.os.Handler;
33import android.os.Looper;
34import android.os.Message;
35import android.os.ParcelFileDescriptor;
36import android.os.RemoteException;
37import android.provider.Telephony;
38import android.provider.Telephony.Mms;
39import android.provider.Telephony.MmsSms;
40import android.provider.Telephony.Sms;
41import android.provider.Telephony.Sms.Inbox;
42import android.telephony.PhoneStateListener;
43import android.telephony.ServiceState;
44import android.telephony.SmsManager;
45import android.telephony.SmsMessage;
46import android.telephony.TelephonyManager;
47import android.text.format.DateUtils;
48import android.util.Log;
49import android.util.Xml;
50
51import org.xmlpull.v1.XmlSerializer;
52
53import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
54import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart;
55import com.android.bluetooth.mapapi.BluetoothMapContract;
56import com.android.bluetooth.mapapi.BluetoothMapContract.MessageColumns;
57import com.google.android.mms.pdu.PduHeaders;
58
59import java.io.FileNotFoundException;
60import java.io.FileOutputStream;
61import java.io.IOException;
62import java.io.OutputStream;
63import java.io.StringWriter;
64import java.io.UnsupportedEncodingException;
65import java.util.ArrayList;
66import java.util.Arrays;
67import java.util.Calendar;
68import java.util.Collections;
69import java.util.HashMap;
70import java.util.HashSet;
71import java.util.Map;
72import java.util.Set;
73
74import javax.obex.ResponseCodes;
75
76@TargetApi(19)
77public class BluetoothMapContentObserver {
78    private static final String TAG = "BluetoothMapContentObserver";
79
80    private static final boolean D = BluetoothMapService.DEBUG;
81    private static final boolean V = BluetoothMapService.VERBOSE;
82
83    private static final String EVENT_TYPE_NEW              = "NewMessage";
84    private static final String EVENT_TYPE_DELETE           = "MessageDeleted";
85    private static final String EVENT_TYPE_REMOVED          = "MessageRemoved";
86    private static final String EVENT_TYPE_SHIFT            = "MessageShift";
87    private static final String EVENT_TYPE_DELEVERY_SUCCESS = "DeliverySuccess";
88    private static final String EVENT_TYPE_SENDING_SUCCESS  = "SendingSuccess";
89    private static final String EVENT_TYPE_SENDING_FAILURE  = "SendingFailure";
90    private static final String EVENT_TYPE_DELIVERY_FAILURE = "DeliveryFailure";
91    private static final String EVENT_TYPE_READ_STATUS      = "ReadStatusChanged";
92    private static final String EVENT_TYPE_CONVERSATION     = "ConversationChanged";
93    private static final String EVENT_TYPE_PRESENCE         = "ParticipantPresenceChanged";
94    private static final String EVENT_TYPE_CHAT_STATE       = "ParticipantChatStateChanged";
95
96    private static final long EVENT_FILTER_NEW_MESSAGE                  = 1L;
97    private static final long EVENT_FILTER_MESSAGE_DELETED              = 1L<<1;
98    private static final long EVENT_FILTER_MESSAGE_SHIFT                = 1L<<2;
99    private static final long EVENT_FILTER_SENDING_SUCCESS              = 1L<<3;
100    private static final long EVENT_FILTER_SENDING_FAILED               = 1L<<4;
101    private static final long EVENT_FILTER_DELIVERY_SUCCESS             = 1L<<5;
102    private static final long EVENT_FILTER_DELIVERY_FAILED              = 1L<<6;
103    private static final long EVENT_FILTER_MEMORY_FULL                  = 1L<<7; // Unused
104    private static final long EVENT_FILTER_MEMORY_AVAILABLE             = 1L<<8; // Unused
105    private static final long EVENT_FILTER_READ_STATUS_CHANGED          = 1L<<9;
106    private static final long EVENT_FILTER_CONVERSATION_CHANGED         = 1L<<10;
107    private static final long EVENT_FILTER_PARTICIPANT_PRESENCE_CHANGED = 1L<<11;
108    private static final long EVENT_FILTER_PARTICIPANT_CHATSTATE_CHANGED= 1L<<12;
109    private static final long EVENT_FILTER_MESSAGE_REMOVED              = 1L<<13;
110
111    // TODO: If we are requesting a large message from the network, on a slow connection
112    //       20 seconds might not be enough... But then again 20 seconds is long for other
113    //       cases.
114    private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS;
115
116    private Context mContext;
117    private ContentResolver mResolver;
118    private ContentProviderClient mProviderClient = null;
119    private BluetoothMnsObexClient mMnsClient;
120    private BluetoothMapMasInstance mMasInstance = null;
121    private int mMasId;
122    private boolean mEnableSmsMms = false;
123    private boolean mObserverRegistered = false;
124    private BluetoothMapAccountItem mAccount;
125    private String mAuthority = null;
126
127    // Default supported feature bit mask is 0x1f
128    private int mMapSupportedFeatures = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
129    // Default event report version is 1.0
130    private int mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V10;
131
132    private BluetoothMapFolderElement mFolders =
133            new BluetoothMapFolderElement("DUMMY", null); // Will be set by the MAS when generated.
134    private Uri mMessageUri = null;
135    private Uri mContactUri = null;
136
137    private boolean mTransmitEvents = true;
138
139    /* To make the filter update atomic, we declare it volatile.
140     * To avoid a penalty when using it, copy the value to a local
141     * non-volatile variable when used more than once.
142     * Actually we only ever use the lower 4 bytes of this variable,
143     * hence we could manage without the volatile keyword, but as
144     * we tend to copy ways of doing things, we better do it right:-) */
145    private volatile long mEventFilter = 0xFFFFFFFFL;
146
147    public static final int DELETED_THREAD_ID = -1;
148
149    // X-Mms-Message-Type field types. These are from PduHeaders.java
150    public static final int MESSAGE_TYPE_RETRIEVE_CONF = 0x84;
151
152    // Text only MMS converted to SMS if sms parts less than or equal to defined count
153    private static final int CONVERT_MMS_TO_SMS_PART_COUNT = 10;
154
155    private TYPE mSmsType;
156
157    private static final String ACTION_MESSAGE_DELIVERY =
158            "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY";
159    /*package*/ static final String ACTION_MESSAGE_SENT =
160        "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT";
161
162    public static final String EXTRA_MESSAGE_SENT_HANDLE = "HANDLE";
163    public static final String EXTRA_MESSAGE_SENT_RESULT = "result";
164    public static final String EXTRA_MESSAGE_SENT_MSG_TYPE = "type";
165    public static final String EXTRA_MESSAGE_SENT_URI = "uri";
166    public static final String EXTRA_MESSAGE_SENT_RETRY = "retry";
167    public static final String EXTRA_MESSAGE_SENT_TRANSPARENT = "transparent";
168    public static final String EXTRA_MESSAGE_SENT_TIMESTAMP = "timestamp";
169
170    private SmsBroadcastReceiver mSmsBroadcastReceiver = new SmsBroadcastReceiver();
171
172    private boolean mInitialized = false;
173
174
175    static final String[] SMS_PROJECTION = new String[] {
176        Sms._ID,
177        Sms.THREAD_ID,
178        Sms.ADDRESS,
179        Sms.BODY,
180        Sms.DATE,
181        Sms.READ,
182        Sms.TYPE,
183        Sms.STATUS,
184        Sms.LOCKED,
185        Sms.ERROR_CODE
186    };
187
188    static final String[] SMS_PROJECTION_SHORT = new String[] {
189        Sms._ID,
190        Sms.THREAD_ID,
191        Sms.TYPE,
192        Sms.READ
193    };
194
195    static final String[] SMS_PROJECTION_SHORT_EXT = new String[] {
196        Sms._ID,
197        Sms.THREAD_ID,
198        Sms.ADDRESS,
199        Sms.BODY,
200        Sms.DATE,
201        Sms.READ,
202        Sms.TYPE,
203    };
204
205    static final String[] MMS_PROJECTION_SHORT = new String[] {
206        Mms._ID,
207        Mms.THREAD_ID,
208        Mms.MESSAGE_TYPE,
209        Mms.MESSAGE_BOX,
210        Mms.READ
211    };
212
213    static final String[] MMS_PROJECTION_SHORT_EXT = new String[] {
214        Mms._ID,
215        Mms.THREAD_ID,
216        Mms.MESSAGE_TYPE,
217        Mms.MESSAGE_BOX,
218        Mms.READ,
219        Mms.DATE,
220        Mms.SUBJECT,
221        Mms.PRIORITY
222    };
223
224    static final String[] MSG_PROJECTION_SHORT = new String[] {
225        BluetoothMapContract.MessageColumns._ID,
226        BluetoothMapContract.MessageColumns.FOLDER_ID,
227        BluetoothMapContract.MessageColumns.FLAG_READ
228    };
229
230    static final String[] MSG_PROJECTION_SHORT_EXT = new String[] {
231        BluetoothMapContract.MessageColumns._ID,
232        BluetoothMapContract.MessageColumns.FOLDER_ID,
233        BluetoothMapContract.MessageColumns.FLAG_READ,
234        BluetoothMapContract.MessageColumns.DATE,
235        BluetoothMapContract.MessageColumns.SUBJECT,
236        BluetoothMapContract.MessageColumns.FROM_LIST,
237        BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY
238    };
239
240    static final String[] MSG_PROJECTION_SHORT_EXT2 = new String[] {
241        BluetoothMapContract.MessageColumns._ID,
242        BluetoothMapContract.MessageColumns.FOLDER_ID,
243        BluetoothMapContract.MessageColumns.FLAG_READ,
244        BluetoothMapContract.MessageColumns.DATE,
245        BluetoothMapContract.MessageColumns.SUBJECT,
246        BluetoothMapContract.MessageColumns.FROM_LIST,
247        BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY,
248        BluetoothMapContract.MessageColumns.THREAD_ID,
249        BluetoothMapContract.MessageColumns.THREAD_NAME
250    };
251
252    public BluetoothMapContentObserver(final Context context,
253            BluetoothMnsObexClient mnsClient,
254            BluetoothMapMasInstance masInstance,
255            BluetoothMapAccountItem account,
256            boolean enableSmsMms) throws RemoteException {
257        mContext = context;
258        mResolver = mContext.getContentResolver();
259        mAccount = account;
260        mMasInstance = masInstance;
261        mMasId = mMasInstance.getMasId();
262
263        mMapSupportedFeatures = mMasInstance.getRemoteFeatureMask();
264        if (D) Log.d(TAG, "BluetoothMapContentObserver: Supported features " +
265                Integer.toHexString(mMapSupportedFeatures) ) ;
266
267        if((BluetoothMapUtils.MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT
268                & mMapSupportedFeatures) != 0){
269            mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
270        }
271        // Make sure support for all formats result in latest version returned
272        if((BluetoothMapUtils.MAP_FEATURE_EVENT_REPORT_V12_BIT
273                & mMapSupportedFeatures) != 0){
274            mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
275        }
276
277        if(account != null) {
278            mAuthority = Uri.parse(account.mBase_uri).getAuthority();
279            mMessageUri = Uri.parse(account.mBase_uri + "/" + BluetoothMapContract.TABLE_MESSAGE);
280            if (mAccount.getType() == TYPE.IM) {
281                mContactUri = Uri.parse(account.mBase_uri + "/"
282                        + BluetoothMapContract.TABLE_CONVOCONTACT);
283            }
284            // TODO: We need to release this again!
285            mProviderClient = mResolver.acquireUnstableContentProviderClient(mAuthority);
286            if (mProviderClient == null) {
287                throw new RemoteException("Failed to acquire provider for " + mAuthority);
288            }
289            mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
290            mContactList = mMasInstance.getContactList();
291            if(mContactList == null) {
292                setContactList(new HashMap<String, BluetoothMapConvoContactElement>(), false);
293                initContactsList();
294            }
295        }
296        mEnableSmsMms = enableSmsMms;
297        mSmsType = getSmsType();
298        mMnsClient = mnsClient;
299        /* Get the cached list - if any, else create */
300        mMsgListSms = mMasInstance.getMsgListSms();
301        boolean doInit = false;
302        if(mEnableSmsMms) {
303            if(mMsgListSms == null) {
304                setMsgListSms(new HashMap<Long, Msg>(), false);
305                doInit = true;
306            }
307            mMsgListMms = mMasInstance.getMsgListMms();
308            if(mMsgListMms == null) {
309                setMsgListMms(new HashMap<Long, Msg>(), false);
310                doInit = true;
311            }
312        }
313        if(mAccount != null) {
314            mMsgListMsg = mMasInstance.getMsgListMsg();
315            if(mMsgListMsg == null) {
316                setMsgListMsg(new HashMap<Long, Msg>(), false);
317                doInit = true;
318            }
319        }
320        if(doInit) {
321            initMsgList();
322        }
323    }
324
325
326    private Map<Long, Msg> getMsgListSms() {
327        return mMsgListSms;
328    }
329
330    private void setMsgListSms(Map<Long, Msg> msgListSms, boolean changesDetected) {
331        mMsgListSms = msgListSms;
332        if(changesDetected) {
333            mMasInstance.updateFolderVersionCounter();
334        }
335        mMasInstance.setMsgListSms(msgListSms);
336    }
337
338
339    private Map<Long, Msg> getMsgListMms() {
340        return mMsgListMms;
341    }
342
343
344    private void setMsgListMms(Map<Long, Msg> msgListMms, boolean changesDetected) {
345        mMsgListMms = msgListMms;
346        if(changesDetected) {
347            mMasInstance.updateFolderVersionCounter();
348        }
349        mMasInstance.setMsgListMms(msgListMms);
350    }
351
352
353    private Map<Long, Msg> getMsgListMsg() {
354        return mMsgListMsg;
355    }
356
357
358    private void setMsgListMsg(Map<Long, Msg> msgListMsg, boolean changesDetected) {
359        mMsgListMsg = msgListMsg;
360        if(changesDetected) {
361            mMasInstance.updateFolderVersionCounter();
362        }
363        mMasInstance.setMsgListMsg(msgListMsg);
364    }
365
366    private Map<String, BluetoothMapConvoContactElement> getContactList() {
367        return mContactList;
368    }
369
370
371    /**
372     * Currently we only have data for IM / email contacts
373     * @param contactList
374     * @param changesDetected that is not chat state changed nor presence state changed.
375     */
376    private void setContactList(Map<String, BluetoothMapConvoContactElement> contactList,
377            boolean changesDetected) {
378        mContactList = contactList;
379        if(changesDetected) {
380            mMasInstance.updateImEmailConvoListVersionCounter();
381        }
382        mMasInstance.setContactList(contactList);
383    }
384
385    private static boolean sendEventNewMessage(long eventFilter) {
386        return ((eventFilter & EVENT_FILTER_NEW_MESSAGE) > 0);
387    }
388
389    private static boolean sendEventMessageDeleted(long eventFilter) {
390        return ((eventFilter & EVENT_FILTER_MESSAGE_DELETED) > 0);
391    }
392
393    private static boolean sendEventMessageShift(long eventFilter) {
394        return ((eventFilter & EVENT_FILTER_MESSAGE_SHIFT) > 0);
395    }
396
397    private static boolean sendEventSendingSuccess(long eventFilter) {
398        return ((eventFilter & EVENT_FILTER_SENDING_SUCCESS) > 0);
399    }
400
401    private static boolean sendEventSendingFailed(long eventFilter) {
402        return ((eventFilter & EVENT_FILTER_SENDING_FAILED) > 0);
403    }
404
405    private static boolean sendEventDeliverySuccess(long eventFilter) {
406        return ((eventFilter & EVENT_FILTER_DELIVERY_SUCCESS) > 0);
407    }
408
409    private static boolean sendEventDeliveryFailed(long eventFilter) {
410        return ((eventFilter & EVENT_FILTER_DELIVERY_FAILED) > 0);
411    }
412
413    private static boolean sendEventReadStatusChanged(long eventFilter) {
414        return ((eventFilter & EVENT_FILTER_READ_STATUS_CHANGED) > 0);
415    }
416
417    private static boolean sendEventConversationChanged(long eventFilter) {
418        return ((eventFilter & EVENT_FILTER_CONVERSATION_CHANGED) > 0);
419    }
420
421    private static boolean sendEventParticipantPresenceChanged(long eventFilter) {
422        return ((eventFilter & EVENT_FILTER_PARTICIPANT_PRESENCE_CHANGED) > 0);
423    }
424
425    private static boolean sendEventParticipantChatstateChanged(long eventFilter) {
426        return ((eventFilter & EVENT_FILTER_PARTICIPANT_CHATSTATE_CHANGED) > 0);
427    }
428
429    private static boolean sendEventMessageRemoved(long eventFilter) {
430        return ((eventFilter & EVENT_FILTER_MESSAGE_REMOVED) > 0);
431    }
432
433    private TYPE getSmsType() {
434        TYPE smsType = null;
435        TelephonyManager tm = (TelephonyManager) mContext.getSystemService(
436                Context.TELEPHONY_SERVICE);
437
438        if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
439            smsType = TYPE.SMS_GSM;
440        } else if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
441            smsType = TYPE.SMS_CDMA;
442        }
443
444        return smsType;
445    }
446
447    private final ContentObserver mObserver = new ContentObserver(
448            new Handler(Looper.getMainLooper())) {
449        @Override
450        public void onChange(boolean selfChange) {
451            onChange(selfChange, null);
452        }
453
454        @Override
455        public void onChange(boolean selfChange, Uri uri) {
456            if(uri == null) {
457                Log.w(TAG, "onChange() with URI == null - not handled.");
458                return;
459            }
460            if (V) Log.d(TAG, "onChange on thread: " + Thread.currentThread().getId()
461                    + " Uri: " + uri.toString() + " selfchange: " + selfChange);
462
463            if(uri.toString().contains(BluetoothMapContract.TABLE_CONVOCONTACT))
464                handleContactListChanges(uri);
465            else
466                handleMsgListChanges(uri);
467        }
468    };
469
470    private static final HashMap<Integer, String> FOLDER_SMS_MAP;
471    static {
472        FOLDER_SMS_MAP = new HashMap<Integer, String>();
473        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_INBOX,  BluetoothMapContract.FOLDER_NAME_INBOX);
474        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_SENT,  BluetoothMapContract.FOLDER_NAME_SENT);
475        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_DRAFT,  BluetoothMapContract.FOLDER_NAME_DRAFT);
476        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_OUTBOX,  BluetoothMapContract.FOLDER_NAME_OUTBOX);
477        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_FAILED,  BluetoothMapContract.FOLDER_NAME_OUTBOX);
478        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_QUEUED,  BluetoothMapContract.FOLDER_NAME_OUTBOX);
479    }
480
481    private static String getSmsFolderName(int type) {
482        String name = FOLDER_SMS_MAP.get(type);
483        if(name != null) {
484            return name;
485        }
486        Log.e(TAG, "New SMS mailbox types have been introduced, without an update in BT...");
487        return "Unknown";
488    }
489
490
491    private static final HashMap<Integer, String> FOLDER_MMS_MAP;
492    static {
493        FOLDER_MMS_MAP = new HashMap<Integer, String>();
494        FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_INBOX,  BluetoothMapContract.FOLDER_NAME_INBOX);
495        FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_SENT,   BluetoothMapContract.FOLDER_NAME_SENT);
496        FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_DRAFTS, BluetoothMapContract.FOLDER_NAME_DRAFT);
497        FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_OUTBOX, BluetoothMapContract.FOLDER_NAME_OUTBOX);
498    }
499
500    private static String getMmsFolderName(int mailbox) {
501        String name = FOLDER_MMS_MAP.get(mailbox);
502        if(name != null) {
503            return name;
504        }
505        Log.e(TAG, "New MMS mailboxes have been introduced, without an update in BT...");
506        return "Unknown";
507    }
508
509    /**
510     * Set the folder structure to be used for this instance.
511     * @param folderStructure
512     */
513    public void setFolderStructure(BluetoothMapFolderElement folderStructure) {
514        this.mFolders = folderStructure;
515    }
516
517    private class ConvoContactInfo {
518        public int mConvoColConvoId         = -1;
519        public int mConvoColLastActivity    = -1;
520        public int mConvoColName            = -1;
521        //        public int mConvoColRead            = -1;
522        //        public int mConvoColVersionCounter  = -1;
523        public int mContactColUci           = -1;
524        public int mContactColConvoId       = -1;
525        public int mContactColName          = -1;
526        public int mContactColNickname      = -1;
527        public int mContactColBtUid         = -1;
528        public int mContactColChatState     = -1;
529        public int mContactColContactId     = -1;
530        public int mContactColLastActive    = -1;
531        public int mContactColPresenceState = -1;
532        public int mContactColPresenceText  = -1;
533        public int mContactColPriority      = -1;
534        public int mContactColLastOnline    = -1;
535
536        public void setConvoColunms(Cursor c) {
537            //            mConvoColConvoId         = c.getColumnIndex(
538            //                    BluetoothMapContract.ConversationColumns.THREAD_ID);
539            //            mConvoColLastActivity    = c.getColumnIndex(
540            //                    BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY);
541            //            mConvoColName            = c.getColumnIndex(
542            //                    BluetoothMapContract.ConversationColumns.THREAD_NAME);
543            mContactColConvoId       = c.getColumnIndex(
544                    BluetoothMapContract.ConvoContactColumns.CONVO_ID);
545            mContactColName          = c.getColumnIndex(
546                    BluetoothMapContract.ConvoContactColumns.NAME);
547            mContactColNickname      = c.getColumnIndex(
548                    BluetoothMapContract.ConvoContactColumns.NICKNAME);
549            mContactColBtUid         = c.getColumnIndex(
550                    BluetoothMapContract.ConvoContactColumns.X_BT_UID);
551            mContactColChatState     = c.getColumnIndex(
552                    BluetoothMapContract.ConvoContactColumns.CHAT_STATE);
553            mContactColUci           = c.getColumnIndex(
554                    BluetoothMapContract.ConvoContactColumns.UCI);
555            mContactColNickname      = c.getColumnIndex(
556                    BluetoothMapContract.ConvoContactColumns.NICKNAME);
557            mContactColLastActive    = c.getColumnIndex(
558                    BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE);
559            mContactColName          = c.getColumnIndex(
560                    BluetoothMapContract.ConvoContactColumns.NAME);
561            mContactColPresenceState = c.getColumnIndex(
562                    BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE);
563            mContactColPresenceText  = c.getColumnIndex(
564                    BluetoothMapContract.ConvoContactColumns.STATUS_TEXT);
565            mContactColPriority      = c.getColumnIndex(
566                    BluetoothMapContract.ConvoContactColumns.PRIORITY);
567            mContactColLastOnline    = c.getColumnIndex(
568                    BluetoothMapContract.ConvoContactColumns.LAST_ONLINE);
569        }
570    }
571
572    private class Event {
573        String eventType;
574        long handle;
575        String folder = null;
576        String oldFolder = null;
577        TYPE msgType;
578        /* Extended event parameters in MAP Event version 1.1 */
579        String datetime = null; // OBEX time "YYYYMMDDTHHMMSS"
580        String uci = null;
581        String subject = null;
582        String senderName = null;
583        String priority = null;
584        /* Event parameters in MAP Event version 1.2 */
585        String conversationName = null;
586        long conversationID = -1;
587        int presenceState = BluetoothMapContract.PresenceState.UNKNOWN;
588        String presenceStatus = null;
589        int chatState = BluetoothMapContract.ChatState.UNKNOWN;
590
591        final static String PATH = "telecom/msg/";
592
593        private void setFolderPath(String name, TYPE type) {
594            if (name != null) {
595                if(type == TYPE.EMAIL || type == TYPE.IM) {
596                    this.folder = name;
597                } else {
598                    this.folder = PATH + name;
599                }
600            } else {
601                this.folder = null;
602            }
603        }
604
605        public Event(String eventType, long handle, String folder,
606                String oldFolder, TYPE msgType) {
607            this.eventType = eventType;
608            this.handle = handle;
609            setFolderPath(folder, msgType);
610            if (oldFolder != null) {
611                if(msgType == TYPE.EMAIL || msgType == TYPE.IM) {
612                    this.oldFolder = oldFolder;
613                } else {
614                    this.oldFolder = PATH + oldFolder;
615                }
616            } else {
617                this.oldFolder = null;
618            }
619            this.msgType = msgType;
620        }
621
622        public Event(String eventType, long handle, String folder, TYPE msgType) {
623            this.eventType = eventType;
624            this.handle = handle;
625            setFolderPath(folder, msgType);
626            this.msgType = msgType;
627        }
628
629        /* extended event type 1.1 */
630        public Event(String eventType, long handle, String folder, TYPE msgType,
631                String datetime, String subject, String senderName, String priority) {
632            this.eventType = eventType;
633            this.handle = handle;
634            setFolderPath(folder, msgType);
635            this.msgType = msgType;
636            this.datetime = datetime;
637            if (subject != null) {
638                this.subject = BluetoothMapUtils.stripInvalidChars(subject);
639            }
640            if (senderName != null) {
641                this.senderName = BluetoothMapUtils.stripInvalidChars(senderName);
642            }
643            this.priority = priority;
644        }
645
646        /* extended event type 1.2 message events */
647        public Event(String eventType, long handle, String folder, TYPE msgType,
648                String datetime, String subject, String senderName, String priority,
649                long conversationID, String conversationName) {
650            this.eventType = eventType;
651            this.handle = handle;
652            setFolderPath(folder, msgType);
653            this.msgType = msgType;
654            this.datetime = datetime;
655            if (subject != null) {
656                this.subject = BluetoothMapUtils.stripInvalidChars(subject);
657            }
658            if (senderName != null) {
659                this.senderName = BluetoothMapUtils.stripInvalidChars(senderName);
660            }
661            if (conversationID != 0) {
662                this.conversationID = conversationID;
663            }
664            if (conversationName != null) {
665                this.conversationName = BluetoothMapUtils.stripInvalidChars(conversationName);
666            }
667            this.priority = priority;
668        }
669
670        /* extended event type 1.2 for conversation, presence or chat state changed events */
671        public Event(String eventType, String uci, TYPE msgType, String name, String priority,
672                String lastActivity, long conversationID, String conversationName,
673                int presenceState, String presenceStatus, int chatState) {
674            this.eventType = eventType;
675            this.uci = uci;
676            this.msgType = msgType;
677            if (name != null) {
678                this.senderName = BluetoothMapUtils.stripInvalidChars(name);
679            }
680            this.priority = priority;
681            this.datetime = lastActivity;
682            if (conversationID != 0) {
683                this.conversationID = conversationID;
684            }
685            if (conversationName != null) {
686                this.conversationName = BluetoothMapUtils.stripInvalidChars(conversationName);
687            }
688            if (presenceState != BluetoothMapContract.PresenceState.UNKNOWN) {
689                this.presenceState = presenceState;
690            }
691            if (presenceStatus != null) {
692                this.presenceStatus = BluetoothMapUtils.stripInvalidChars(presenceStatus);
693            }
694            if (chatState != BluetoothMapContract.ChatState.UNKNOWN) {
695                this.chatState = chatState;
696            }
697        }
698
699        public byte[] encode() throws UnsupportedEncodingException {
700            StringWriter sw = new StringWriter();
701            XmlSerializer xmlEvtReport = Xml.newSerializer();
702
703            try {
704                xmlEvtReport.setOutput(sw);
705                xmlEvtReport.startDocument("UTF-8", true);
706                xmlEvtReport.text("\r\n");
707                xmlEvtReport.startTag("", "MAP-event-report");
708                if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
709                    xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V10_STR);
710                } else if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
711                    xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V11_STR);
712                } else {
713                    xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V12_STR);
714                }
715                xmlEvtReport.startTag("", "event");
716                xmlEvtReport.attribute("", "type", eventType);
717                if (eventType.equals(EVENT_TYPE_CONVERSATION) ||
718                        eventType.equals(EVENT_TYPE_PRESENCE) ||
719                        eventType.equals(EVENT_TYPE_CHAT_STATE)) {
720                    xmlEvtReport.attribute("", "participant_uci", uci);
721                } else {
722                    xmlEvtReport.attribute("", "handle",
723                            BluetoothMapUtils.getMapHandle(handle, msgType));
724                }
725
726                if (folder != null) {
727                    xmlEvtReport.attribute("", "folder", folder);
728                }
729                if (oldFolder != null) {
730                    xmlEvtReport.attribute("", "old_folder", oldFolder);
731                }
732                xmlEvtReport.attribute("", "msg_type", msgType.name());
733
734                /* If MAP event report version is above 1.0 send
735                 * extended event report parameters */
736                if (datetime != null) {
737                    xmlEvtReport.attribute("", "datetime", datetime);
738                }
739                if (subject != null) {
740                    xmlEvtReport.attribute("", "subject",
741                            subject.substring(0,subject.length() < 256 ? subject.length() : 256));
742                }
743                if (senderName != null) {
744                    xmlEvtReport.attribute("", "senderName", senderName);
745                }
746                if (priority != null) {
747                    xmlEvtReport.attribute("", "priority", priority);
748                }
749
750                //}
751                /* Include conversation information from event version 1.2 */
752                if (mMapEventReportVersion > BluetoothMapUtils.MAP_EVENT_REPORT_V11 ) {
753                    if (conversationName != null) {
754                        xmlEvtReport.attribute("", "conversation_name", conversationName);
755                    }
756                    if (conversationID != -1) {
757                        // Convert provider conversation handle to string incl type
758                        xmlEvtReport.attribute("", "conversation_id",
759                                BluetoothMapUtils.getMapConvoHandle(conversationID, msgType));
760                    }
761                    if (eventType.equals(EVENT_TYPE_PRESENCE)) {
762                        if (presenceState != 0) {
763                            // Convert provider conversation handle to string incl type
764                            xmlEvtReport.attribute("", "presence_availability",
765                                    String.valueOf(presenceState));
766                        }
767                        if (presenceStatus != null) {
768                            // Convert provider conversation handle to string incl type
769                            xmlEvtReport.attribute("", "presence_status",
770                                    presenceStatus.substring(
771                                            0,presenceStatus.length() < 256 ? subject.length() : 256));
772                        }
773                    }
774                    if (eventType.equals(EVENT_TYPE_PRESENCE)) {
775                        if (chatState != 0) {
776                            // Convert provider conversation handle to string incl type
777                            xmlEvtReport.attribute("", "chat_state", String.valueOf(chatState));
778                        }
779                    }
780
781                }
782                xmlEvtReport.endTag("", "event");
783                xmlEvtReport.endTag("", "MAP-event-report");
784                xmlEvtReport.endDocument();
785            } catch (IllegalArgumentException e) {
786                if(D) Log.w(TAG,e);
787            } catch (IllegalStateException e) {
788                if(D) Log.w(TAG,e);
789            } catch (IOException e) {
790                if(D) Log.w(TAG,e);
791            }
792
793            if (V) Log.d(TAG, sw.toString());
794
795            return sw.toString().getBytes("UTF-8");
796        }
797    }
798
799    /*package*/ class Msg {
800        long id;
801        int type;               // Used as folder for SMS/MMS
802        int threadId;           // Used for SMS/MMS at delete
803        long folderId = -1;     // Email folder ID
804        long oldFolderId = -1;  // Used for email undelete
805        boolean localInitiatedSend = false; // Used for MMS to filter out events
806        boolean transparent = false; // Used for EMAIL to delete message sent with transparency
807        int flagRead = -1;      // Message status read/unread
808
809        public Msg(long id, int type, int threadId, int readFlag) {
810            this.id = id;
811            this.type = type;
812            this.threadId = threadId;
813            this.flagRead = readFlag;
814        }
815        public Msg(long id, long folderId, int readFlag) {
816            this.id = id;
817            this.folderId = folderId;
818            this.flagRead = readFlag;
819        }
820
821        /* Eclipse generated hashCode() and equals() to make
822         * hashMap lookup work independent of whether the obj
823         * is used for email or SMS/MMS and whether or not the
824         * oldFolder is set. */
825        @Override
826        public int hashCode() {
827            final int prime = 31;
828            int result = 1;
829            result = prime * result + (int) (id ^ (id >>> 32));
830            return result;
831        }
832
833        @Override
834        public boolean equals(Object obj) {
835            if (this == obj)
836                return true;
837            if (obj == null)
838                return false;
839            if (getClass() != obj.getClass())
840                return false;
841            Msg other = (Msg) obj;
842            if (id != other.id)
843                return false;
844            return true;
845        }
846    }
847
848    private Map<Long, Msg> mMsgListSms = null;
849
850    private Map<Long, Msg> mMsgListMms = null;
851
852    private Map<Long, Msg> mMsgListMsg = null;
853
854    private Map<String, BluetoothMapConvoContactElement> mContactList = null;
855
856    public int setNotificationRegistration(int notificationStatus) throws RemoteException {
857        // Forward the request to the MNS thread as a message - including the MAS instance ID.
858        if(D) Log.d(TAG,"setNotificationRegistration() enter");
859        Handler mns = mMnsClient.getMessageHandler();
860        if(mns != null) {
861            Message msg = mns.obtainMessage();
862            msg.what = BluetoothMnsObexClient.MSG_MNS_NOTIFICATION_REGISTRATION;
863            msg.arg1 = mMasId;
864            msg.arg2 = notificationStatus;
865            mns.sendMessageDelayed(msg, 10); // Send message without forcing a context switch
866            /* Some devices - e.g. PTS needs to get the unregister confirm before we actually
867             * disconnect the MNS. */
868            if(D) Log.d(TAG,"setNotificationRegistration() MSG_MNS_NOTIFICATION_REGISTRATION " +
869                    "send to MNS");
870        } else {
871            // This should not happen except at shutdown.
872            if(D) Log.d(TAG,"setNotificationRegistration() Unable to send registration request");
873            return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
874        }
875        if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) {
876            registerObserver();
877        } else {
878            unregisterObserver();
879        }
880        return ResponseCodes.OBEX_HTTP_OK;
881    }
882
883    boolean eventMaskContainsContacts(long mask) {
884        return sendEventParticipantPresenceChanged(mask);
885    }
886
887    boolean eventMaskContainsCovo(long mask) {
888        return (sendEventConversationChanged(mask)
889                || sendEventParticipantChatstateChanged(mask));
890    }
891
892    /* Overwrite the existing notification filter. Will register/deregister observers for
893     * the Contacts and Conversation table as needed. We keep the message observer
894     * at all times. */
895    /*package*/ synchronized void setNotificationFilter(long newFilter) {
896        long oldFilter = mEventFilter;
897        mEventFilter = newFilter;
898        /* Contacts */
899        if(!eventMaskContainsContacts(oldFilter) &&
900                eventMaskContainsContacts(newFilter)) {
901            // TODO:
902            // Enable the observer
903            // Reset the contacts list
904        }
905        /* Conversations */
906        if(!eventMaskContainsCovo(oldFilter) &&
907                eventMaskContainsCovo(newFilter)) {
908            // TODO:
909            // Enable the observer
910            // Reset the conversations list
911        }
912    }
913
914    public void registerObserver() throws RemoteException{
915        if (V) Log.d(TAG, "registerObserver");
916
917        if (mObserverRegistered)
918            return;
919
920        if(mAccount != null) {
921
922            mProviderClient = mResolver.acquireUnstableContentProviderClient(mAuthority);
923            if (mProviderClient == null) {
924                throw new RemoteException("Failed to acquire provider for " + mAuthority);
925            }
926            mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
927
928            // If there is a change in the database before we init the lists we will be sending
929            // loads of events - hence init before register.
930            if(mAccount.getType() == TYPE.IM) {
931                // Further add contact list tracking
932                initContactsList();
933            }
934        }
935        // If there is a change in the database before we init the lists we will be sending
936        // loads of events - hence init before register.
937        initMsgList();
938
939        /* Use MmsSms Uri since the Sms Uri is not notified on deletes */
940        if(mEnableSmsMms){
941            //this is sms/mms
942            mResolver.registerContentObserver(MmsSms.CONTENT_URI, false, mObserver);
943            mObserverRegistered = true;
944        }
945
946        if(mAccount != null) {
947            /* For URI's without account ID */
948            Uri uri = Uri.parse(mAccount.mBase_uri_no_account + "/"
949                    + BluetoothMapContract.TABLE_MESSAGE);
950            if(D) Log.d(TAG, "Registering observer for: " + uri);
951            mResolver.registerContentObserver(uri, true, mObserver);
952
953            /* For URI's with account ID - is handled the same way as without ID, but is
954             * only triggered for MAS instances with matching account ID. */
955            uri = Uri.parse(mAccount.mBase_uri + "/" + BluetoothMapContract.TABLE_MESSAGE);
956            if(D) Log.d(TAG, "Registering observer for: " + uri);
957            mResolver.registerContentObserver(uri, true, mObserver);
958
959            if(mAccount.getType() == TYPE.IM) {
960
961                uri = Uri.parse(mAccount.mBase_uri_no_account + "/"
962                        + BluetoothMapContract.TABLE_CONVOCONTACT);
963                if(D) Log.d(TAG, "Registering observer for: " + uri);
964                mResolver.registerContentObserver(uri, true, mObserver);
965
966                /* For URI's with account ID - is handled the same way as without ID, but is
967                 * only triggered for MAS instances with matching account ID. */
968                uri = Uri.parse(mAccount.mBase_uri + "/" + BluetoothMapContract.TABLE_CONVOCONTACT);
969                if(D) Log.d(TAG, "Registering observer for: " + uri);
970                mResolver.registerContentObserver(uri, true, mObserver);
971            }
972
973            mObserverRegistered = true;
974        }
975    }
976
977    public void unregisterObserver() {
978        if (V) Log.d(TAG, "unregisterObserver");
979        mResolver.unregisterContentObserver(mObserver);
980        mObserverRegistered = false;
981        if(mProviderClient != null){
982            mProviderClient.release();
983            mProviderClient = null;
984        }
985    }
986
987    /**
988     * Per design it is only possible to call the refreshXxxx functions sequentially, hence it
989     * is safe to modify mTransmitEvents without synchronization.
990     */
991    /* package */ void refreshFolderVersionCounter() {
992        if (mObserverRegistered) {
993            // As we have observers, we already keep the counter up-to-date.
994            return;
995        }
996        /* We need to perform the same functionality, as when we receive a notification change,
997           hence we:
998            - disable the event transmission
999            - triggers the code for updates
1000            - enable the event transmission */
1001        mTransmitEvents = false;
1002        try {
1003            if(mEnableSmsMms) {
1004                handleMsgListChangesSms();
1005                handleMsgListChangesMms();
1006            }
1007            if(mAccount != null) {
1008                try {
1009                    handleMsgListChangesMsg(mMessageUri);
1010                } catch (RemoteException e) {
1011                    Log.e(TAG, "Unable to update FolderVersionCounter. - Not fatal, but can cause" +
1012                            " undesirable user experience!", e);
1013                }
1014            }
1015        } finally {
1016            // Ensure we always enable events again
1017            mTransmitEvents = true;
1018        }
1019    }
1020
1021    /* package */ void refreshConvoListVersionCounter() {
1022        if (mObserverRegistered) {
1023            // As we have observers, we already keep the counter up-to-date.
1024            return;
1025        }
1026        /* We need to perform the same functionality, as when we receive a notification change,
1027        hence we:
1028         - disable event transmission
1029         - triggers the code for updates
1030         - enable event transmission */
1031        mTransmitEvents = false;
1032        try {
1033            if((mAccount != null) && (mContactUri != null)) {
1034                handleContactListChanges(mContactUri);
1035            }
1036        } finally {
1037            // Ensure we always enable events again
1038            mTransmitEvents = true;
1039        }
1040    }
1041
1042    private void sendEvent(Event evt) {
1043
1044        if(mTransmitEvents == false) {
1045            if(V) Log.v(TAG, "mTransmitEvents == false - don't send event.");
1046            return;
1047        }
1048
1049        if(D)Log.d(TAG, "sendEvent: " + evt.eventType + " " + evt.handle + " " + evt.folder + " "
1050                + evt.oldFolder + " " + evt.msgType.name() + " " + evt.datetime + " "
1051                + evt.subject + " " + evt.senderName + " " + evt.priority );
1052
1053        if (mMnsClient == null || mMnsClient.isConnected() == false) {
1054            Log.d(TAG, "sendEvent: No MNS client registered or connected- don't send event");
1055            return;
1056        }
1057
1058        /* Enable use of the cache for checking the filter */
1059        long eventFilter = mEventFilter;
1060
1061        /* This should have been a switch on the string, but it is not allowed in Java 1.6 */
1062        /* WARNING: Here we do pointer compare for the string to speed up things, that is.
1063         * HENCE: always use the EVENT_TYPE_"defines" */
1064        if(evt.eventType == EVENT_TYPE_NEW) {
1065            if(!sendEventNewMessage(eventFilter)) {
1066                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
1067                return;
1068            }
1069        } else if(evt.eventType == EVENT_TYPE_DELETE) {
1070            if(!sendEventMessageDeleted(eventFilter)) {
1071                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
1072                return;
1073            }
1074        } else if(evt.eventType == EVENT_TYPE_REMOVED) {
1075            if(!sendEventMessageRemoved(eventFilter)) {
1076                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
1077                return;
1078            }
1079        } else if(evt.eventType == EVENT_TYPE_SHIFT) {
1080            if(!sendEventMessageShift(eventFilter)) {
1081                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
1082                return;
1083            }
1084        } else if(evt.eventType == EVENT_TYPE_DELEVERY_SUCCESS) {
1085            if(!sendEventDeliverySuccess(eventFilter)) {
1086                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
1087                return;
1088            }
1089        } else if(evt.eventType == EVENT_TYPE_SENDING_SUCCESS) {
1090            if(!sendEventSendingSuccess(eventFilter)) {
1091                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
1092                return;
1093            }
1094        } else if(evt.eventType == EVENT_TYPE_SENDING_FAILURE) {
1095            if(!sendEventSendingFailed(eventFilter)) {
1096                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
1097                return;
1098            }
1099        } else if(evt.eventType == EVENT_TYPE_DELIVERY_FAILURE) {
1100            if(!sendEventDeliveryFailed(eventFilter)) {
1101                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
1102                return;
1103            }
1104        } else if(evt.eventType == EVENT_TYPE_READ_STATUS) {
1105            if(!sendEventReadStatusChanged(eventFilter)) {
1106                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
1107                return;
1108            }
1109        } else if(evt.eventType == EVENT_TYPE_CONVERSATION) {
1110            if(!sendEventConversationChanged(eventFilter)) {
1111                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
1112                return;
1113            }
1114        } else if(evt.eventType == EVENT_TYPE_PRESENCE) {
1115            if(!sendEventParticipantPresenceChanged(eventFilter)) {
1116                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
1117                return;
1118            }
1119        } else if(evt.eventType == EVENT_TYPE_CHAT_STATE) {
1120            if(!sendEventParticipantChatstateChanged(eventFilter)) {
1121                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
1122                return;
1123            }
1124        }
1125
1126        try {
1127            mMnsClient.sendEvent(evt.encode(), mMasId);
1128        } catch (UnsupportedEncodingException ex) {
1129            /* do nothing */
1130            if (D) Log.e(TAG, "Exception - should not happen: ",ex);
1131        }
1132    }
1133
1134    private void initMsgList() throws RemoteException {
1135        if (V) Log.d(TAG, "initMsgList");
1136
1137        if(mEnableSmsMms) {
1138
1139            HashMap<Long, Msg> msgListSms = new HashMap<Long, Msg>();
1140
1141            Cursor c = mResolver.query(Sms.CONTENT_URI,
1142                    SMS_PROJECTION_SHORT, null, null, null);
1143            try {
1144                if (c != null && c.moveToFirst()) {
1145                    do {
1146                        long id = c.getLong(c.getColumnIndex(Sms._ID));
1147                        int type = c.getInt(c.getColumnIndex(Sms.TYPE));
1148                        int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
1149                        int read = c.getInt(c.getColumnIndex(Sms.READ));
1150
1151                        Msg msg = new Msg(id, type, threadId, read);
1152                        msgListSms.put(id, msg);
1153                    } while (c.moveToNext());
1154                }
1155            } finally {
1156                if (c != null) c.close();
1157            }
1158
1159            synchronized(getMsgListSms()) {
1160                getMsgListSms().clear();
1161                setMsgListSms(msgListSms, true); // Set initial folder version counter
1162            }
1163
1164            HashMap<Long, Msg> msgListMms = new HashMap<Long, Msg>();
1165
1166            c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION_SHORT, null, null, null);
1167            try {
1168                if (c != null && c.moveToFirst()) {
1169                    do {
1170                        long id = c.getLong(c.getColumnIndex(Mms._ID));
1171                        int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
1172                        int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
1173                        int read = c.getInt(c.getColumnIndex(Mms.READ));
1174
1175                        Msg msg = new Msg(id, type, threadId, read);
1176                        msgListMms.put(id, msg);
1177                    } while (c.moveToNext());
1178                }
1179            } finally {
1180                if (c != null) c.close();
1181            }
1182
1183            synchronized(getMsgListMms()) {
1184                getMsgListMms().clear();
1185                setMsgListMms(msgListMms, true); // Set initial folder version counter
1186            }
1187        }
1188
1189        if(mAccount != null) {
1190            HashMap<Long, Msg> msgList = new HashMap<Long, Msg>();
1191            Uri uri = mMessageUri;
1192            Cursor c = mProviderClient.query(uri, MSG_PROJECTION_SHORT, null, null, null);
1193
1194            try {
1195                if (c != null && c.moveToFirst()) {
1196                    do {
1197                        long id = c.getLong(c.getColumnIndex(MessageColumns._ID));
1198                        long folderId = c.getInt(c.getColumnIndex(
1199                                BluetoothMapContract.MessageColumns.FOLDER_ID));
1200                        int readFlag = c.getInt(c.getColumnIndex(
1201                                BluetoothMapContract.MessageColumns.FLAG_READ));
1202                        Msg msg = new Msg(id, folderId, readFlag);
1203                        msgList.put(id, msg);
1204                    } while (c.moveToNext());
1205                }
1206            } finally {
1207                if (c != null) c.close();
1208            }
1209
1210            synchronized(getMsgListMsg()) {
1211                getMsgListMsg().clear();
1212                setMsgListMsg(msgList, true);
1213            }
1214        }
1215    }
1216
1217    private void initContactsList() throws RemoteException {
1218        if (V) Log.d(TAG, "initContactsList");
1219        if(mContactUri == null) {
1220            if (D) Log.d(TAG, "initContactsList() no mContactUri - nothing to init");
1221            return;
1222        }
1223        Uri uri = mContactUri;
1224        Cursor c = mProviderClient.query(uri,
1225                BluetoothMapContract.BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION,
1226                null, null, null);
1227        Map<String, BluetoothMapConvoContactElement> contactList =
1228                new HashMap<String, BluetoothMapConvoContactElement>();
1229        try {
1230            if (c != null && c.moveToFirst()) {
1231                ConvoContactInfo cInfo = new ConvoContactInfo();
1232                cInfo.setConvoColunms(c);
1233                do {
1234                    long convoId = c.getLong(cInfo.mContactColConvoId);
1235                    if (convoId == 0)
1236                        continue;
1237                    if (V) BluetoothMapUtils.printCursor(c);
1238                    String uci = c.getString(cInfo.mContactColUci);
1239                    String name = c.getString(cInfo.mContactColName);
1240                    String displayName = c.getString(cInfo.mContactColNickname);
1241                    String presenceStatus = c.getString(cInfo.mContactColPresenceText);
1242                    int presenceState = c.getInt(cInfo.mContactColPresenceState);
1243                    long lastActivity = c.getLong(cInfo.mContactColLastActive);
1244                    int chatState = c.getInt(cInfo.mContactColChatState);
1245                    int priority = c.getInt(cInfo.mContactColPriority);
1246                    String btUid = c.getString(cInfo.mContactColBtUid);
1247                    BluetoothMapConvoContactElement contact =
1248                            new BluetoothMapConvoContactElement(uci, name, displayName,
1249                                    presenceStatus, presenceState, lastActivity, chatState,
1250                                    priority, btUid);
1251                    contactList.put(uci, contact);
1252                } while (c.moveToNext());
1253            }
1254        } finally {
1255            if (c != null) c.close();
1256        }
1257        synchronized(getContactList()) {
1258            getContactList().clear();
1259            setContactList(contactList, true);
1260        }
1261    }
1262
1263    private void handleMsgListChangesSms() {
1264        if (V) Log.d(TAG, "handleMsgListChangesSms");
1265
1266        HashMap<Long, Msg> msgListSms = new HashMap<Long, Msg>();
1267        boolean listChanged = false;
1268
1269        Cursor c;
1270        if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
1271            c = mResolver.query(Sms.CONTENT_URI,
1272                    SMS_PROJECTION_SHORT, null, null, null);
1273        } else {
1274            c = mResolver.query(Sms.CONTENT_URI,
1275                    SMS_PROJECTION_SHORT_EXT, null, null, null);
1276        }
1277        synchronized(getMsgListSms()) {
1278            try {
1279                if (c != null && c.moveToFirst()) {
1280                    do {
1281                        long id = c.getLong(c.getColumnIndex(Sms._ID));
1282                        int type = c.getInt(c.getColumnIndex(Sms.TYPE));
1283                        int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
1284                        int read = c.getInt(c.getColumnIndex(Sms.READ));
1285
1286                        Msg msg = getMsgListSms().remove(id);
1287
1288                        /* We must filter out any actions made by the MCE, hence do not send e.g.
1289                         * a message deleted and/or MessageShift for messages deleted by the MCE. */
1290
1291                        if (msg == null) {
1292                            /* New message */
1293                            msg = new Msg(id, type, threadId, read);
1294                            msgListSms.put(id, msg);
1295                            listChanged = true;
1296                            Event evt;
1297                            if (mTransmitEvents == true && // extract contact details only if needed
1298                                    mMapEventReportVersion >
1299                            BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
1300                                String date = BluetoothMapUtils.getDateTimeString(
1301                                        c.getLong(c.getColumnIndex(Sms.DATE)));
1302                                String subject = c.getString(c.getColumnIndex(Sms.BODY));
1303                                String name = "";
1304                                String phone = "";
1305                                if (type == 1) { //inbox
1306                                    phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
1307                                    if (phone != null && !phone.isEmpty()) {
1308                                        name = BluetoothMapContent.getContactNameFromPhone(phone,
1309                                                mResolver);
1310                                        if(name == null || name.isEmpty()){
1311                                            name = phone;
1312                                        }
1313                                    }else{
1314                                        name = phone;
1315                                    }
1316                                } else {
1317                                    TelephonyManager tm =
1318                                            (TelephonyManager)mContext.getSystemService(
1319                                            Context.TELEPHONY_SERVICE);
1320                                    if (tm != null) {
1321                                        phone = tm.getLine1Number();
1322                                        name = tm.getLine1AlphaTag();
1323                                        if(name == null || name.isEmpty()){
1324                                            name = phone;
1325                                        }
1326                                    }
1327                                }
1328                                String priority = "no";// no priority for sms
1329                                /* Incoming message from the network */
1330                                if (mMapEventReportVersion ==
1331                                        BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
1332                                    evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type),
1333                                            mSmsType, date, subject, name, priority);
1334                                } else {
1335                                    evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type),
1336                                            mSmsType, date, subject, name, priority,
1337                                            (long)threadId, null);
1338                                }
1339                            } else {
1340                                /* Incoming message from the network */
1341                                evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type),
1342                                        null, mSmsType);
1343                            }
1344                            sendEvent(evt);
1345                        } else {
1346                            /* Existing message */
1347                            if (type != msg.type) {
1348                                listChanged = true;
1349                                Log.d(TAG, "new type: " + type + " old type: " + msg.type);
1350                                String oldFolder = getSmsFolderName(msg.type);
1351                                String newFolder = getSmsFolderName(type);
1352                                // Filter out the intermediate outbox steps
1353                                if(!oldFolder.equalsIgnoreCase(newFolder)) {
1354                                    Event evt = new Event(EVENT_TYPE_SHIFT, id,
1355                                            getSmsFolderName(type), oldFolder, mSmsType);
1356                                    sendEvent(evt);
1357                                }
1358                                msg.type = type;
1359                            } else if(threadId != msg.threadId) {
1360                                listChanged = true;
1361                                Log.d(TAG, "Message delete change: type: " + type
1362                                        + " old type: " + msg.type
1363                                        + "\n    threadId: " + threadId
1364                                        + " old threadId: " + msg.threadId);
1365                                if(threadId == DELETED_THREAD_ID) { // Message deleted
1366                                    // TODO:
1367                                    // We shall only use the folder attribute, but can't remember
1368                                    // wether to set it to "deleted" or the name of the folder
1369                                    // from which the message have been deleted.
1370                                    Event evt = new Event(EVENT_TYPE_DELETE, id,
1371                                            BluetoothMapContract.FOLDER_NAME_DELETED,
1372                                            getSmsFolderName(msg.type), mSmsType);
1373                                    sendEvent(evt);
1374                                    msg.threadId = threadId;
1375                                } else { // Undelete
1376                                    Event evt = new Event(EVENT_TYPE_SHIFT, id,
1377                                            getSmsFolderName(msg.type),
1378                                            BluetoothMapContract.FOLDER_NAME_DELETED, mSmsType);
1379                                    sendEvent(evt);
1380                                    msg.threadId = threadId;
1381                                }
1382                            }
1383                            if(read != msg.flagRead) {
1384                                listChanged = true;
1385                                msg.flagRead = read;
1386                                if (mMapEventReportVersion >
1387                                        BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
1388                                    Event evt = new Event(EVENT_TYPE_READ_STATUS, id,
1389                                            getSmsFolderName(msg.type), mSmsType);
1390                                    sendEvent(evt);
1391                                }
1392                            }
1393                            msgListSms.put(id, msg);
1394                        }
1395                    } while (c.moveToNext());
1396                }
1397            } finally {
1398                if (c != null) c.close();
1399            }
1400
1401            for (Msg msg : getMsgListSms().values()) {
1402                Event evt = new Event(EVENT_TYPE_DELETE, msg.id,
1403                        BluetoothMapContract.FOLDER_NAME_DELETED,
1404                        getSmsFolderName(msg.type), mSmsType);
1405                sendEvent(evt);
1406                listChanged = true;
1407            }
1408
1409            setMsgListSms(msgListSms, listChanged);
1410        }
1411    }
1412
1413    private void handleMsgListChangesMms() {
1414        if (V) Log.d(TAG, "handleMsgListChangesMms");
1415
1416        HashMap<Long, Msg> msgListMms = new HashMap<Long, Msg>();
1417        boolean listChanged = false;
1418        Cursor c;
1419        if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
1420            c = mResolver.query(Mms.CONTENT_URI,
1421                    MMS_PROJECTION_SHORT, null, null, null);
1422        } else {
1423            c = mResolver.query(Mms.CONTENT_URI,
1424                    MMS_PROJECTION_SHORT_EXT, null, null, null);
1425        }
1426
1427        synchronized(getMsgListMms()) {
1428            try{
1429                if (c != null && c.moveToFirst()) {
1430                    do {
1431                        long id = c.getLong(c.getColumnIndex(Mms._ID));
1432                        int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
1433                        int mtype = c.getInt(c.getColumnIndex(Mms.MESSAGE_TYPE));
1434                        int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
1435                        // TODO: Go through code to see if we have an issue with mismatch in types
1436                        //       for threadId. Seems to be a long in DB??
1437                        int read = c.getInt(c.getColumnIndex(Mms.READ));
1438
1439                        Msg msg = getMsgListMms().remove(id);
1440
1441                        /* We must filter out any actions made by the MCE, hence do not send
1442                         * e.g. a message deleted and/or MessageShift for messages deleted by the
1443                         * MCE.*/
1444
1445                        if (msg == null) {
1446                            /* New message - only notify on retrieve conf */
1447                            listChanged = true;
1448                            if (getMmsFolderName(type).equalsIgnoreCase(
1449                                    BluetoothMapContract.FOLDER_NAME_INBOX) &&
1450                                    mtype != MESSAGE_TYPE_RETRIEVE_CONF) {
1451                                continue;
1452                            }
1453                            msg = new Msg(id, type, threadId, read);
1454                            msgListMms.put(id, msg);
1455                            Event evt;
1456                            if (mTransmitEvents == true && // extract contact details only if needed
1457                                    mMapEventReportVersion !=
1458                                    BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
1459                                String date = BluetoothMapUtils.getDateTimeString(
1460                                        c.getLong(c.getColumnIndex(Mms.DATE)));
1461                                String subject = c.getString(c.getColumnIndex(Mms.SUBJECT));
1462                                if (subject == null || subject.length() == 0) {
1463                                    /* Get subject from mms text body parts - if any exists */
1464                                    subject = BluetoothMapContent.getTextPartsMms(mResolver, id);
1465                                }
1466                                int tmpPri = c.getInt(c.getColumnIndex(Mms.PRIORITY));
1467                                Log.d(TAG, "TEMP handleMsgListChangesMms, " +
1468                                        "newMessage 'read' state: " + read +
1469                                        "priority: " + tmpPri);
1470
1471                                String address = BluetoothMapContent.getAddressMms(
1472                                        mResolver,id,BluetoothMapContent.MMS_FROM);
1473                                String priority = "no";
1474                                if(tmpPri == PduHeaders.PRIORITY_HIGH)
1475                                    priority = "yes";
1476
1477                                /* Incoming message from the network */
1478                                if (mMapEventReportVersion ==
1479                                        BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
1480                                    evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type),
1481                                            TYPE.MMS, date, subject, address, priority);
1482                                } else {
1483                                    evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type),
1484                                            TYPE.MMS, date, subject, address, priority,
1485                                            (long)threadId, null);
1486                                }
1487
1488                            } else {
1489                                /* Incoming message from the network */
1490                                evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type),
1491                                        null, TYPE.MMS);
1492                            }
1493
1494                            sendEvent(evt);
1495                        } else {
1496                            /* Existing message */
1497                            if (type != msg.type) {
1498                                Log.d(TAG, "new type: " + type + " old type: " + msg.type);
1499                                Event evt;
1500                                listChanged = true;
1501                                if(msg.localInitiatedSend == false) {
1502                                    // Only send events about local initiated changes
1503                                    evt = new Event(EVENT_TYPE_SHIFT, id, getMmsFolderName(type),
1504                                            getMmsFolderName(msg.type), TYPE.MMS);
1505                                    sendEvent(evt);
1506                                }
1507                                msg.type = type;
1508
1509                                if (getMmsFolderName(type).equalsIgnoreCase(
1510                                        BluetoothMapContract.FOLDER_NAME_SENT)
1511                                        && msg.localInitiatedSend == true) {
1512                                    // Stop tracking changes for this message
1513                                    msg.localInitiatedSend = false;
1514                                    evt = new Event(EVENT_TYPE_SENDING_SUCCESS, id,
1515                                            getMmsFolderName(type), null, TYPE.MMS);
1516                                    sendEvent(evt);
1517                                }
1518                            } else if(threadId != msg.threadId) {
1519                                Log.d(TAG, "Message delete change: type: " + type + " old type: "
1520                                        + msg.type
1521                                        + "\n    threadId: " + threadId + " old threadId: "
1522                                        + msg.threadId);
1523                                listChanged = true;
1524                                if(threadId == DELETED_THREAD_ID) { // Message deleted
1525                                    Event evt = new Event(EVENT_TYPE_DELETE, id,
1526                                            BluetoothMapContract.FOLDER_NAME_DELETED,
1527                                            getMmsFolderName(msg.type), TYPE.MMS);
1528                                    sendEvent(evt);
1529                                    msg.threadId = threadId;
1530                                } else { // Undelete
1531                                    Event evt = new Event(EVENT_TYPE_SHIFT, id,
1532                                            getMmsFolderName(msg.type),
1533                                            BluetoothMapContract.FOLDER_NAME_DELETED, TYPE.MMS);
1534                                    sendEvent(evt);
1535                                    msg.threadId = threadId;
1536                                }
1537                            }
1538                            if(read != msg.flagRead) {
1539                                listChanged = true;
1540                                msg.flagRead = read;
1541                                if (mMapEventReportVersion >
1542                                        BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
1543                                    Event evt = new Event(EVENT_TYPE_READ_STATUS, id,
1544                                            getMmsFolderName(msg.type), TYPE.MMS);
1545                                    sendEvent(evt);
1546                                }
1547                            }
1548                            msgListMms.put(id, msg);
1549                        }
1550                    } while (c.moveToNext());
1551
1552                }
1553            } finally {
1554                if (c != null) c.close();
1555            }
1556            for (Msg msg : getMsgListMms().values()) {
1557                Event evt = new Event(EVENT_TYPE_DELETE, msg.id,
1558                        BluetoothMapContract.FOLDER_NAME_DELETED,
1559                        getMmsFolderName(msg.type), TYPE.MMS);
1560                sendEvent(evt);
1561                listChanged = true;
1562            }
1563            setMsgListMms(msgListMms, listChanged);
1564        }
1565    }
1566
1567    private void handleMsgListChangesMsg(Uri uri)  throws RemoteException{
1568        if (V) Log.v(TAG, "handleMsgListChangesMsg uri: " + uri.toString());
1569
1570        // TODO: Change observer to handle accountId and message ID if present
1571
1572        HashMap<Long, Msg> msgList = new HashMap<Long, Msg>();
1573        Cursor c;
1574        boolean listChanged = false;
1575        if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
1576            c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT, null, null, null);
1577        } else if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
1578            c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT_EXT, null, null, null);
1579        } else {
1580            c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT_EXT2, null, null, null);
1581        }
1582        synchronized(getMsgListMsg()) {
1583            try {
1584                if (c != null && c.moveToFirst()) {
1585                    do {
1586                        long id = c.getLong(c.getColumnIndex(
1587                                BluetoothMapContract.MessageColumns._ID));
1588                        int folderId = c.getInt(c.getColumnIndex(
1589                                BluetoothMapContract.MessageColumns.FOLDER_ID));
1590                        int readFlag = c.getInt(c.getColumnIndex(
1591                                BluetoothMapContract.MessageColumns.FLAG_READ));
1592                        Msg msg = getMsgListMsg().remove(id);
1593                        BluetoothMapFolderElement folderElement = mFolders.getFolderById(folderId);
1594                        String newFolder;
1595                        if(folderElement != null) {
1596                            newFolder = folderElement.getFullPath();
1597                        } else {
1598                            // This can happen if a new folder is created while connected
1599                            newFolder = "unknown";
1600                        }
1601                        /* We must filter out any actions made by the MCE, hence do not send e.g.
1602                         * a message deleted and/or MessageShift for messages deleted by the MCE. */
1603                        if (msg == null) {
1604                            listChanged = true;
1605                            /* New message - created with message unread */
1606                            msg = new Msg(id, folderId, 0, readFlag);
1607                            msgList.put(id, msg);
1608                            Event evt;
1609                            /* Incoming message from the network */
1610                            if (mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
1611                                String date = BluetoothMapUtils.getDateTimeString(
1612                                        c.getLong(c.getColumnIndex(
1613                                                BluetoothMapContract.MessageColumns.DATE)));
1614                                String subject = c.getString(c.getColumnIndex(
1615                                        BluetoothMapContract.MessageColumns.SUBJECT));
1616                                String address = c.getString(c.getColumnIndex(
1617                                        BluetoothMapContract.MessageColumns.FROM_LIST));
1618                                String priority = "no";
1619                                if(c.getInt(c.getColumnIndex(
1620                                        BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY))
1621                                        == 1)
1622                                    priority = "yes";
1623                                if (mMapEventReportVersion ==
1624                                        BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
1625                                    evt = new Event(EVENT_TYPE_NEW, id, newFolder,
1626                                            mAccount.getType(), date, subject, address, priority);
1627                                } else {
1628                                    long thread_id = c.getLong(c.getColumnIndex(
1629                                            BluetoothMapContract.MessageColumns.THREAD_ID));
1630                                    String thread_name = c.getString(c.getColumnIndex(
1631                                            BluetoothMapContract.MessageColumns.THREAD_NAME));
1632                                    evt = new Event(EVENT_TYPE_NEW, id, newFolder,
1633                                            mAccount.getType(), date, subject, address, priority,
1634                                            thread_id, thread_name);
1635                                }
1636                            } else {
1637                                evt = new Event(EVENT_TYPE_NEW, id, newFolder, null, TYPE.EMAIL);
1638                            }
1639                            sendEvent(evt);
1640                        } else {
1641                            /* Existing message */
1642                            if (folderId != msg.folderId && msg.folderId != -1) {
1643                                if (D) Log.d(TAG, "new folderId: " + folderId + " old folderId: "
1644                                        + msg.folderId);
1645                                BluetoothMapFolderElement oldFolderElement =
1646                                        mFolders.getFolderById(msg.folderId);
1647                                String oldFolder;
1648                                listChanged = true;
1649                                if(oldFolderElement != null) {
1650                                    oldFolder = oldFolderElement.getFullPath();
1651                                } else {
1652                                    // This can happen if a new folder is created while connected
1653                                    oldFolder = "unknown";
1654                                }
1655                                BluetoothMapFolderElement deletedFolder =
1656                                        mFolders.getFolderByName(
1657                                                BluetoothMapContract.FOLDER_NAME_DELETED);
1658                                BluetoothMapFolderElement sentFolder =
1659                                        mFolders.getFolderByName(
1660                                                BluetoothMapContract.FOLDER_NAME_SENT);
1661                                /*
1662                                 *  If the folder is now 'deleted', send a deleted-event in stead of
1663                                 *  a shift or if message is sent initiated by MAP Client, then send
1664                                 *  sending-success otherwise send folderShift
1665                                 */
1666                                if(deletedFolder != null && deletedFolder.getFolderId()
1667                                        == folderId) {
1668                                    Event evt = new Event(EVENT_TYPE_DELETE, msg.id, newFolder,
1669                                            oldFolder, mAccount.getType());
1670                                    sendEvent(evt);
1671                                } else if(sentFolder != null
1672                                        && sentFolder.getFolderId() == folderId
1673                                        && msg.localInitiatedSend == true) {
1674                                    if(msg.transparent) {
1675                                        mResolver.delete(
1676                                                ContentUris.withAppendedId(mMessageUri, id),
1677                                                null, null);
1678                                    } else {
1679                                        msg.localInitiatedSend = false;
1680                                        Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msg.id,
1681                                                oldFolder, null, mAccount.getType());
1682                                        sendEvent(evt);
1683                                    }
1684                                } else {
1685                                    if (!oldFolder.equalsIgnoreCase("root")) {
1686                                        Event evt = new Event(EVENT_TYPE_SHIFT, id, newFolder,
1687                                                oldFolder, mAccount.getType());
1688                                        sendEvent(evt);
1689                                    }
1690                                }
1691                                msg.folderId = folderId;
1692                            }
1693                            if(readFlag != msg.flagRead) {
1694                                listChanged = true;
1695
1696                                if (mMapEventReportVersion >
1697                                BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
1698                                    Event evt = new Event(EVENT_TYPE_READ_STATUS, id, newFolder,
1699                                            mAccount.getType());
1700                                    sendEvent(evt);
1701                                    msg.flagRead = readFlag;
1702                                }
1703                            }
1704
1705                            msgList.put(id, msg);
1706                        }
1707                    } while (c.moveToNext());
1708                }
1709            } finally {
1710                if (c != null) c.close();
1711            }
1712            // For all messages no longer in the database send a delete notification
1713            for (Msg msg : getMsgListMsg().values()) {
1714                BluetoothMapFolderElement oldFolderElement = mFolders.getFolderById(msg.folderId);
1715                String oldFolder;
1716                listChanged = true;
1717                if(oldFolderElement != null) {
1718                    oldFolder = oldFolderElement.getFullPath();
1719                } else {
1720                    oldFolder = "unknown";
1721                }
1722                /* Some e-mail clients delete the message after sending, and creates a
1723                 * new message in sent. We cannot track the message anymore, hence send both a
1724                 * send success and delete message.
1725                 */
1726                if(msg.localInitiatedSend == true) {
1727                    msg.localInitiatedSend = false;
1728                    // If message is send with transparency don't set folder as message is deleted
1729                    if (msg.transparent)
1730                        oldFolder = null;
1731                    Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msg.id, oldFolder, null,
1732                            mAccount.getType());
1733                    sendEvent(evt);
1734                }
1735                /* As this message deleted is only send on a real delete - don't set folder.
1736                 *  - only send delete event if message is not sent with transparency
1737                 */
1738                if (!msg.transparent) {
1739
1740                    Event evt = new Event(EVENT_TYPE_DELETE, msg.id, null, oldFolder,
1741                            mAccount.getType());
1742                    sendEvent(evt);
1743                }
1744            }
1745            setMsgListMsg(msgList, listChanged);
1746        }
1747    }
1748
1749    private void handleMsgListChanges(Uri uri) {
1750        if(uri.getAuthority().equals(mAuthority)) {
1751            try {
1752                if(D) Log.d(TAG, "handleMsgListChanges: account type = "
1753                        + mAccount.getType().toString());
1754                handleMsgListChangesMsg(uri);
1755            } catch(RemoteException e) {
1756                mMasInstance.restartObexServerSession();
1757                Log.w(TAG, "Problems contacting the ContentProvider in mas Instance "
1758                        + mMasId + " restaring ObexServerSession");
1759            }
1760
1761        }
1762        // TODO: check to see if there could be problem with IM and SMS in one instance
1763        if (mEnableSmsMms) {
1764            handleMsgListChangesSms();
1765            handleMsgListChangesMms();
1766        }
1767    }
1768
1769    private void handleContactListChanges(Uri uri) {
1770        if (uri.getAuthority().equals(mAuthority)) {
1771            try {
1772                if (V) Log.v(TAG,"handleContactListChanges uri: " + uri.toString());
1773                Cursor c = null;
1774                boolean listChanged = false;
1775                try {
1776                    ConvoContactInfo cInfo = new ConvoContactInfo();
1777
1778                    if (mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V10
1779                            && mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
1780                        c = mProviderClient
1781                                .query(mContactUri,
1782                                        BluetoothMapContract.
1783                                        BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION,
1784                                        null, null, null);
1785                        cInfo.setConvoColunms(c);
1786                    } else {
1787                        if (V) Log.v(TAG,"handleContactListChanges MAP version does not" +
1788                                "support convocontact notifications");
1789                        return;
1790                    }
1791
1792                    HashMap<String, BluetoothMapConvoContactElement> contactList =
1793                            new HashMap<String,
1794                            BluetoothMapConvoContactElement>(getContactList().size());
1795
1796                    synchronized (getContactList()) {
1797                        if (c != null && c.moveToFirst()) {
1798                            do {
1799                                String uci = c.getString(cInfo.mContactColUci);
1800                                long convoId = c.getLong(cInfo.mContactColConvoId);
1801                                if (convoId == 0)
1802                                    continue;
1803
1804                                if (V) BluetoothMapUtils.printCursor(c);
1805
1806                                BluetoothMapConvoContactElement contact =
1807                                        getContactList().remove(uci);
1808
1809                                /*
1810                                 * We must filter out any actions made by the
1811                                 * MCE, hence do not send e.g. a message deleted
1812                                 * and/or MessageShift for messages deleted by
1813                                 * the MCE.
1814                                 */
1815                                if (contact == null) {
1816                                    listChanged = true;
1817                                    /*
1818                                     * New contact - added to conversation and
1819                                     * tracked here
1820                                     */
1821                                    if (mMapEventReportVersion
1822                                            != BluetoothMapUtils.MAP_EVENT_REPORT_V10
1823                                            && mMapEventReportVersion
1824                                            != BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
1825                                        Event evt;
1826                                        String name = c
1827                                                .getString(cInfo.mContactColName);
1828                                        String displayName = c
1829                                                .getString(cInfo.mContactColNickname);
1830                                        String presenceStatus = c
1831                                                .getString(cInfo.mContactColPresenceText);
1832                                        int presenceState = c
1833                                                .getInt(cInfo.mContactColPresenceState);
1834                                        long lastActivity = c
1835                                                .getLong(cInfo.mContactColLastActive);
1836                                        int chatState = c
1837                                                .getInt(cInfo.mContactColChatState);
1838                                        int priority = c
1839                                                .getInt(cInfo.mContactColPriority);
1840                                        String btUid = c
1841                                                .getString(cInfo.mContactColBtUid);
1842
1843                                        // Get Conversation information for
1844                                        // event
1845//                                        Uri convoUri = Uri
1846//                                                .parse(mAccount.mBase_uri
1847//                                                        + "/"
1848//                                                        + BluetoothMapContract.TABLE_CONVERSATION);
1849//                                        String whereClause = "contacts._id = "
1850//                                                + convoId;
1851//                                        Cursor cConvo = mProviderClient
1852//                                                .query(convoUri,
1853//                                                       BluetoothMapContract.BT_CONVERSATION_PROJECTION,
1854//                                                       whereClause, null, null);
1855                                        // TODO: will move out of the loop when merged with CB's
1856                                        // changes make sure to look up col index out side loop
1857                                        String convoName = null;
1858//                                        if (cConvo != null
1859//                                                && cConvo.moveToFirst()) {
1860//                                            convoName = cConvo
1861//                                                    .getString(cConvo
1862//                                                            .getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME));
1863//                                        }
1864
1865                                        contact = new BluetoothMapConvoContactElement(
1866                                                uci, name, displayName,
1867                                                presenceStatus, presenceState,
1868                                                lastActivity, chatState,
1869                                                priority, btUid);
1870
1871                                        contactList.put(uci, contact);
1872
1873                                        evt = new Event(
1874                                                EVENT_TYPE_CONVERSATION,
1875                                                uci,
1876                                                mAccount.getType(),
1877                                                name,
1878                                                String.valueOf(priority),
1879                                                BluetoothMapUtils
1880                                                .getDateTimeString(lastActivity),
1881                                                convoId, convoName,
1882                                                presenceState, presenceStatus,
1883                                                chatState);
1884
1885                                        sendEvent(evt);
1886                                    }
1887
1888                                } else {
1889                                    // Not new - compare updated content
1890//                                    Uri convoUri = Uri
1891//                                            .parse(mAccount.mBase_uri
1892//                                                    + "/"
1893//                                                    + BluetoothMapContract.TABLE_CONVERSATION);
1894                                    // TODO: Should be changed to own provider interface name
1895//                                    String whereClause = "contacts._id = "
1896//                                            + convoId;
1897//                                    Cursor cConvo = mProviderClient
1898//                                            .query(convoUri,
1899//                                                    BluetoothMapContract.BT_CONVERSATION_PROJECTION,
1900//                                                    whereClause, null, null);
1901//                                    // TODO: will move out of the loop when merged with CB's
1902//                                    // changes make sure to look up col index out side loop
1903                                    String convoName = null;
1904//                                    if (cConvo != null && cConvo.moveToFirst()) {
1905//                                        convoName = cConvo
1906//                                                .getString(cConvo
1907//                                                        .getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME));
1908//                                    }
1909
1910                                    // Check if presence is updated
1911                                    int presenceState = c.getInt(cInfo.mContactColPresenceState);
1912                                    String presenceStatus = c.getString(
1913                                            cInfo.mContactColPresenceText);
1914                                    String currentPresenceStatus = contact
1915                                            .getPresenceStatus();
1916                                    if (contact.getPresenceAvailability() != presenceState
1917                                            || currentPresenceStatus != presenceStatus) {
1918                                        long lastOnline = c
1919                                                .getLong(cInfo.mContactColLastOnline);
1920                                        contact.setPresenceAvailability(presenceState);
1921                                        contact.setLastActivity(lastOnline);
1922                                        if (currentPresenceStatus != null
1923                                                && !currentPresenceStatus
1924                                                .equals(presenceStatus)) {
1925                                            contact.setPresenceStatus(presenceStatus);
1926                                        }
1927                                        Event evt = new Event(
1928                                                EVENT_TYPE_PRESENCE,
1929                                                uci,
1930                                                mAccount.getType(),
1931                                                contact.getName(),
1932                                                String.valueOf(contact
1933                                                        .getPriority()),
1934                                                        BluetoothMapUtils
1935                                                        .getDateTimeString(lastOnline),
1936                                                        convoId, convoName,
1937                                                        presenceState, presenceStatus,
1938                                                        0);
1939                                        sendEvent(evt);
1940                                    }
1941
1942                                    // Check if chat state is updated
1943                                    int chatState = c.getInt(cInfo.mContactColChatState);
1944                                    if (contact.getChatState() != chatState) {
1945                                        // Get DB timestamp
1946                                        long lastActivity = c.getLong(cInfo.mContactColLastActive);
1947                                        contact.setLastActivity(lastActivity);
1948                                        contact.setChatState(chatState);
1949                                        Event evt = new Event(
1950                                                EVENT_TYPE_CHAT_STATE,
1951                                                uci,
1952                                                mAccount.getType(),
1953                                                contact.getName(),
1954                                                String.valueOf(contact
1955                                                        .getPriority()),
1956                                                        BluetoothMapUtils
1957                                                        .getDateTimeString(lastActivity),
1958                                                        convoId, convoName, 0, null,
1959                                                        chatState);
1960                                        sendEvent(evt);
1961                                    }
1962                                    contactList.put(uci, contact);
1963                                }
1964                            } while (c.moveToNext());
1965                        }
1966                        if(getContactList().size() > 0) {
1967                            // one or more contacts were deleted, hence the conversation listing
1968                            // version counter should change.
1969                            listChanged = true;
1970                        }
1971                        setContactList(contactList, listChanged);
1972                    } // end synchronized
1973                } finally {
1974                    if (c != null) c.close();
1975                }
1976            } catch (RemoteException e) {
1977                mMasInstance.restartObexServerSession();
1978                Log.w(TAG,
1979                        "Problems contacting the ContentProvider in mas Instance "
1980                                + mMasId + " restaring ObexServerSession");
1981            }
1982
1983        }
1984        // TODO: conversation contact updates if IM and SMS(MMS in one instance
1985    }
1986
1987    private boolean setEmailMessageStatusDelete(BluetoothMapFolderElement mCurrentFolder,
1988            String uriStr, long handle, int status) {
1989        boolean res = false;
1990        Uri uri = Uri.parse(uriStr + BluetoothMapContract.TABLE_MESSAGE);
1991
1992        int updateCount = 0;
1993        ContentValues contentValues = new ContentValues();
1994        BluetoothMapFolderElement deleteFolder = mFolders.
1995                getFolderByName(BluetoothMapContract.FOLDER_NAME_DELETED);
1996        contentValues.put(BluetoothMapContract.MessageColumns._ID, handle);
1997        synchronized(getMsgListMsg()) {
1998            Msg msg = getMsgListMsg().get(handle);
1999            if (status == BluetoothMapAppParams.STATUS_VALUE_YES) {
2000                /* Set deleted folder id */
2001                long folderId = -1;
2002                if(deleteFolder != null) {
2003                    folderId = deleteFolder.getFolderId();
2004                }
2005                contentValues.put(BluetoothMapContract.MessageColumns.FOLDER_ID,folderId);
2006                updateCount = mResolver.update(uri, contentValues, null, null);
2007                /* The race between updating the value in our cached values and the database
2008                 * is handled by the synchronized statement. */
2009                if(updateCount > 0) {
2010                    res = true;
2011                    if (msg != null) {
2012                        msg.oldFolderId = msg.folderId;
2013                        /* Update the folder ID to avoid triggering an event for MCE
2014                         * initiated actions. */
2015                        msg.folderId = folderId;
2016                    }
2017                    if(D) Log.d(TAG, "Deleted MSG: " + handle + " from folderId: " + folderId);
2018                } else {
2019                    Log.w(TAG, "Msg: " + handle + " - Set delete status " + status
2020                            + " failed for folderId " + folderId);
2021                }
2022            } else if (status == BluetoothMapAppParams.STATUS_VALUE_NO) {
2023                /* Undelete message. move to old folder if we know it,
2024                 * else move to inbox - as dictated by the spec. */
2025                if(msg != null && deleteFolder != null &&
2026                        msg.folderId == deleteFolder.getFolderId()) {
2027                    /* Only modify messages in the 'Deleted' folder */
2028                    long folderId = -1;
2029                    BluetoothMapFolderElement inboxFolder = mCurrentFolder.
2030                            getFolderByName(BluetoothMapContract.FOLDER_NAME_INBOX);
2031                    if (msg != null && msg.oldFolderId != -1) {
2032                        folderId = msg.oldFolderId;
2033                    } else {
2034                        if(inboxFolder != null) {
2035                            folderId = inboxFolder.getFolderId();
2036                        }
2037                        if(D)Log.d(TAG,"We did not delete the message, hence the old folder " +
2038                                "is unknown. Moving to inbox.");
2039                    }
2040                    contentValues.put(BluetoothMapContract.MessageColumns.FOLDER_ID, folderId);
2041                    updateCount = mResolver.update(uri, contentValues, null, null);
2042                    if(updateCount > 0) {
2043                        res = true;
2044                        /* Update the folder ID to avoid triggering an event for MCE
2045                         * initiated actions. */
2046                        /* UPDATE: Actually the BT-Spec. states that an undelete is a move of the
2047                         * message to INBOX - clearified in errata 5591.
2048                         * Therefore we update the cache to INBOX-folderId - to trigger a message
2049                         * shift event to the old-folder. */
2050                        if(inboxFolder != null) {
2051                            msg.folderId = inboxFolder.getFolderId();
2052                        } else {
2053                            msg.folderId = folderId;
2054                        }
2055                    } else {
2056                        if(D)Log.d(TAG,"We did not delete the message, hence the old folder " +
2057                                "is unknown. Moving to inbox.");
2058                    }
2059                }
2060            }
2061            if(V) {
2062                BluetoothMapFolderElement folderElement;
2063                String folderName = "unknown";
2064                if (msg != null) {
2065                    folderElement = mCurrentFolder.getFolderById(msg.folderId);
2066                    if(folderElement != null) {
2067                        folderName = folderElement.getName();
2068                    }
2069                }
2070                Log.d(TAG,"setEmailMessageStatusDelete: " + handle + " from " + folderName
2071                        + " status: " + status);
2072            }
2073        }
2074        if(res == false) {
2075            Log.w(TAG, "Set delete status " + status + " failed.");
2076        }
2077        return res;
2078    }
2079
2080    private void updateThreadId(Uri uri, String valueString, long threadId) {
2081        ContentValues contentValues = new ContentValues();
2082        contentValues.put(valueString, threadId);
2083        mResolver.update(uri, contentValues, null, null);
2084    }
2085
2086    private boolean deleteMessageMms(long handle) {
2087        boolean res = false;
2088        Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
2089        Cursor c = mResolver.query(uri, null, null, null, null);
2090        try {
2091            if (c != null && c.moveToFirst()) {
2092                /* Move to deleted folder, or delete if already in deleted folder */
2093                int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
2094                if (threadId != DELETED_THREAD_ID) {
2095                    /* Set deleted thread id */
2096                    synchronized(getMsgListMms()) {
2097                        Msg msg = getMsgListMms().get(handle);
2098                        if(msg != null) { // This will always be the case
2099                            msg.threadId = DELETED_THREAD_ID;
2100                        }
2101                    }
2102                    updateThreadId(uri, Mms.THREAD_ID, DELETED_THREAD_ID);
2103                } else {
2104                    /* Delete from observer message list to avoid delete notifications */
2105                    synchronized(getMsgListMms()) {
2106                        getMsgListMms().remove(handle);
2107                    }
2108                    /* Delete message */
2109                    mResolver.delete(uri, null, null);
2110                }
2111                res = true;
2112            }
2113        } finally {
2114            if (c != null) c.close();
2115        }
2116
2117        return res;
2118    }
2119
2120    private boolean unDeleteMessageMms(long handle) {
2121        boolean res = false;
2122        Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
2123        Cursor c = mResolver.query(uri, null, null, null, null);
2124        try {
2125            if (c != null && c.moveToFirst()) {
2126                int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
2127                if (threadId == DELETED_THREAD_ID) {
2128                    /* Restore thread id from address, or if no thread for address
2129                     * create new thread by insert and remove of fake message */
2130                    String address;
2131                    long id = c.getLong(c.getColumnIndex(Mms._ID));
2132                    int msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
2133                    if (msgBox == Mms.MESSAGE_BOX_INBOX) {
2134                        address = BluetoothMapContent.getAddressMms(mResolver, id,
2135                                BluetoothMapContent.MMS_FROM);
2136                    } else {
2137                        address = BluetoothMapContent.getAddressMms(mResolver, id,
2138                                BluetoothMapContent.MMS_TO);
2139                    }
2140                    Set<String> recipients = new HashSet<String>();
2141                    recipients.addAll(Arrays.asList(address));
2142                    Long oldThreadId = Telephony.Threads.getOrCreateThreadId(mContext, recipients);
2143                    synchronized(getMsgListMms()) {
2144                        Msg msg = getMsgListMms().get(handle);
2145                        if(msg != null) { // This will always be the case
2146                            msg.threadId = oldThreadId.intValue();
2147                            // Spec. states that undelete shall shift the message to Inbox.
2148                            // Hence we need to trigger a message shift from INBOX to old-folder
2149                            // after undelete.
2150                            // We do this by changing the cached folder value to being inbox - hence
2151                            // the event handler will se the update as the message have been shifted
2152                            // from INBOX to old-folder. (Errata 5591 clearifies this)
2153                            msg.type = Mms.MESSAGE_BOX_INBOX;
2154                        }
2155                    }
2156                    updateThreadId(uri, Mms.THREAD_ID, oldThreadId);
2157                } else {
2158                    Log.d(TAG, "Message not in deleted folder: handle " + handle
2159                            + " threadId " + threadId);
2160                }
2161                res = true;
2162            }
2163        } finally {
2164            if (c != null) c.close();
2165        }
2166        return res;
2167    }
2168
2169    private boolean deleteMessageSms(long handle) {
2170        boolean res = false;
2171        Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle);
2172        Cursor c = mResolver.query(uri, null, null, null, null);
2173        try {
2174            if (c != null && c.moveToFirst()) {
2175                /* Move to deleted folder, or delete if already in deleted folder */
2176                int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
2177                if (threadId != DELETED_THREAD_ID) {
2178                    synchronized(getMsgListSms()) {
2179                        Msg msg = getMsgListSms().get(handle);
2180                        if(msg != null) { // This will always be the case
2181                            msg.threadId = DELETED_THREAD_ID;
2182                        }
2183                    }
2184                    /* Set deleted thread id */
2185                    updateThreadId(uri, Sms.THREAD_ID, DELETED_THREAD_ID);
2186                } else {
2187                    /* Delete from observer message list to avoid delete notifications */
2188                    synchronized(getMsgListSms()) {
2189                        getMsgListSms().remove(handle);
2190                    }
2191                    /* Delete message */
2192                    mResolver.delete(uri, null, null);
2193                }
2194                res = true;
2195            }
2196        } finally {
2197            if (c != null) c.close();
2198        }
2199        return res;
2200    }
2201
2202    private boolean unDeleteMessageSms(long handle) {
2203        boolean res = false;
2204        Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle);
2205        Cursor c = mResolver.query(uri, null, null, null, null);
2206        try {
2207            if (c != null && c.moveToFirst()) {
2208                int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
2209                if (threadId == DELETED_THREAD_ID) {
2210                    String address = c.getString(c.getColumnIndex(Sms.ADDRESS));
2211                    Set<String> recipients = new HashSet<String>();
2212                    recipients.addAll(Arrays.asList(address));
2213                    Long oldThreadId = Telephony.Threads.getOrCreateThreadId(mContext, recipients);
2214                    synchronized(getMsgListSms()) {
2215                        Msg msg = getMsgListSms().get(handle);
2216                        if(msg != null) {
2217                            msg.threadId = oldThreadId.intValue();
2218                            /* This will always be the case
2219                             * The threadId is specified as an int, so it is safe to truncate
2220                             * TODO: Test that this will trigger a message-shift from Inbox
2221                             * to old-folder
2222                             **/
2223                            /* Spec. states that undelete shall shift the message to Inbox.
2224                             * Hence we need to trigger a message shift from INBOX to old-folder
2225                             * after undelete.
2226                             * We do this by changing the cached folder value to being inbox - hence
2227                             * the event handler will se the update as the message have been shifted
2228                             * from INBOX to old-folder. (Errata 5591 clearifies this)
2229                             * */
2230                            msg.type = Sms.MESSAGE_TYPE_INBOX;
2231                        }
2232                    }
2233                    updateThreadId(uri, Sms.THREAD_ID, oldThreadId);
2234                } else {
2235                    Log.d(TAG, "Message not in deleted folder: handle " + handle
2236                            + " threadId " + threadId);
2237                }
2238                res = true;
2239            }
2240        } finally {
2241            if (c != null) c.close();
2242        }
2243        return res;
2244    }
2245
2246    /**
2247     *
2248     * @param handle
2249     * @param type
2250     * @param mCurrentFolder
2251     * @param uriStr
2252     * @param statusValue
2253     * @return true is success
2254     */
2255    public boolean setMessageStatusDeleted(long handle, TYPE type,
2256            BluetoothMapFolderElement mCurrentFolder, String uriStr, int statusValue) {
2257        boolean res = false;
2258        if (D) Log.d(TAG, "setMessageStatusDeleted: handle " + handle
2259                + " type " + type + " value " + statusValue);
2260
2261        if (type == TYPE.EMAIL) {
2262            res = setEmailMessageStatusDelete(mCurrentFolder, uriStr, handle, statusValue);
2263        } else if (type == TYPE.IM) {
2264            // TODO: to do when deleting IM message
2265            if (D) Log.d(TAG, "setMessageStatusDeleted: IM not handled" );
2266        } else {
2267            if (statusValue == BluetoothMapAppParams.STATUS_VALUE_YES) {
2268                if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) {
2269                    res = deleteMessageSms(handle);
2270                } else if (type == TYPE.MMS) {
2271                    res = deleteMessageMms(handle);
2272                }
2273            } else if (statusValue == BluetoothMapAppParams.STATUS_VALUE_NO) {
2274                if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) {
2275                    res = unDeleteMessageSms(handle);
2276                } else if (type == TYPE.MMS) {
2277                    res = unDeleteMessageMms(handle);
2278                }
2279            }
2280        }
2281        return res;
2282    }
2283
2284    /**
2285     *
2286     * @param handle
2287     * @param type
2288     * @param uriStr
2289     * @param statusValue
2290     * @return true at success
2291     */
2292    public boolean setMessageStatusRead(long handle, TYPE type, String uriStr, int statusValue)
2293            throws RemoteException{
2294        int count = 0;
2295
2296        if (D) Log.d(TAG, "setMessageStatusRead: handle " + handle
2297                + " type " + type + " value " + statusValue);
2298
2299        /* Approved MAP spec errata 3445 states that read status initiated
2300         * by the MCE shall change the MSE read status. */
2301        if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) {
2302            Uri uri = Sms.Inbox.CONTENT_URI;
2303            ContentValues contentValues = new ContentValues();
2304            contentValues.put(Sms.READ, statusValue);
2305            contentValues.put(Sms.SEEN, statusValue);
2306            String where = Sms._ID+"="+handle;
2307            String values = contentValues.toString();
2308            if (D) Log.d(TAG, " -> SMS Uri: " + uri.toString() +
2309                    " Where " + where + " values " + values);
2310            synchronized(getMsgListSms()) {
2311                Msg msg = getMsgListSms().get(handle);
2312                if(msg != null) { // This will always be the case
2313                    msg.flagRead = statusValue;
2314                }
2315            }
2316            count = mResolver.update(uri, contentValues, where, null);
2317            if (D) Log.d(TAG, " -> "+count +" rows updated!");
2318
2319        } else if (type == TYPE.MMS) {
2320            Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
2321            if (D) Log.d(TAG, " -> MMS Uri: " + uri.toString());
2322            ContentValues contentValues = new ContentValues();
2323            contentValues.put(Mms.READ, statusValue);
2324            synchronized(getMsgListMms()) {
2325                Msg msg = getMsgListMms().get(handle);
2326                if(msg != null) { // This will always be the case
2327                    msg.flagRead = statusValue;
2328                }
2329            }
2330            count = mResolver.update(uri, contentValues, null, null);
2331            if (D) Log.d(TAG, " -> "+count +" rows updated!");
2332        } else if (type == TYPE.EMAIL ||
2333                type == TYPE.IM) {
2334            Uri uri = mMessageUri;
2335            ContentValues contentValues = new ContentValues();
2336            contentValues.put(BluetoothMapContract.MessageColumns.FLAG_READ, statusValue);
2337            contentValues.put(BluetoothMapContract.MessageColumns._ID, handle);
2338            synchronized(getMsgListMsg()) {
2339                Msg msg = getMsgListMsg().get(handle);
2340                if(msg != null) { // This will always be the case
2341                    msg.flagRead = statusValue;
2342                }
2343            }
2344            count = mProviderClient.update(uri, contentValues, null, null);
2345        }
2346
2347        return (count > 0);
2348    }
2349
2350    private class PushMsgInfo {
2351        long id;
2352        int transparent;
2353        int retry;
2354        String phone;
2355        Uri uri;
2356        long timestamp;
2357        int parts;
2358        int partsSent;
2359        int partsDelivered;
2360        boolean resend;
2361        boolean sendInProgress;
2362        boolean failedSent; // Set to true if a single part sent fail is received.
2363        int statusDelivered; // Set to != 0 if a single part deliver fail is received.
2364
2365        public PushMsgInfo(long id, int transparent,
2366                int retry, String phone, Uri uri) {
2367            this.id = id;
2368            this.transparent = transparent;
2369            this.retry = retry;
2370            this.phone = phone;
2371            this.uri = uri;
2372            this.resend = false;
2373            this.sendInProgress = false;
2374            this.failedSent = false;
2375            this.statusDelivered = 0; /* Assume success */
2376            this.timestamp = 0;
2377        };
2378    }
2379
2380    private Map<Long, PushMsgInfo> mPushMsgList =
2381            Collections.synchronizedMap(new HashMap<Long, PushMsgInfo>());
2382
2383    public long pushMessage(BluetoothMapbMessage msg, BluetoothMapFolderElement folderElement,
2384            BluetoothMapAppParams ap, String emailBaseUri)
2385                    throws IllegalArgumentException, RemoteException, IOException {
2386        if (D) Log.d(TAG, "pushMessage");
2387        ArrayList<BluetoothMapbMessage.vCard> recipientList = msg.getRecipients();
2388        int transparent = (ap.getTransparent() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) ?
2389                0 : ap.getTransparent();
2390        int retry = ap.getRetry();
2391        int charset = ap.getCharset();
2392        long handle = -1;
2393        long folderId = -1;
2394
2395        if (recipientList == null) {
2396            if (D) Log.d(TAG, "empty recipient list");
2397            return -1;
2398        }
2399
2400        if ( msg.getType().equals(TYPE.EMAIL) ) {
2401            /* Write the message to the database */
2402            String msgBody = ((BluetoothMapbMessageEmail) msg).getEmailBody();
2403            if (V) {
2404                int length = msgBody.length();
2405                Log.v(TAG, "pushMessage: message string length = " + length);
2406                String messages[] = msgBody.split("\r\n");
2407                Log.v(TAG, "pushMessage: messages count=" + messages.length);
2408                for(int i = 0; i < messages.length; i++) {
2409                    Log.v(TAG, "part " + i + ":" + messages[i]);
2410                }
2411            }
2412            FileOutputStream os = null;
2413            ParcelFileDescriptor fdOut = null;
2414            Uri uriInsert = Uri.parse(emailBaseUri + BluetoothMapContract.TABLE_MESSAGE);
2415            if (D) Log.d(TAG, "pushMessage - uriInsert= " + uriInsert.toString() +
2416                    ", intoFolder id=" + folderElement.getFolderId());
2417
2418            synchronized(getMsgListMsg()) {
2419                // Now insert the empty message into folder
2420                ContentValues values = new ContentValues();
2421                folderId = folderElement.getFolderId();
2422                values.put(BluetoothMapContract.MessageColumns.FOLDER_ID, folderId);
2423                Uri uriNew = mProviderClient.insert(uriInsert, values);
2424                if (D) Log.d(TAG, "pushMessage - uriNew= " + uriNew.toString());
2425                handle =  Long.parseLong(uriNew.getLastPathSegment());
2426
2427                try {
2428                    fdOut = mProviderClient.openFile(uriNew, "w");
2429                    os = new FileOutputStream(fdOut.getFileDescriptor());
2430                    // Write Email to DB
2431                    os.write(msgBody.getBytes(), 0, msgBody.getBytes().length);
2432                } catch (FileNotFoundException e) {
2433                    Log.w(TAG, e);
2434                    throw(new IOException("Unable to open file stream"));
2435                } catch (NullPointerException e) {
2436                    Log.w(TAG, e);
2437                    throw(new IllegalArgumentException("Unable to parse message."));
2438                } finally {
2439                    try {
2440                        if(os != null)
2441                            os.close();
2442                    } catch (IOException e) {Log.w(TAG, e);}
2443                    try {
2444                        if(fdOut != null)
2445                            fdOut.close();
2446                    } catch (IOException e) {Log.w(TAG, e);}
2447                }
2448
2449                /* Extract the data for the inserted message, and store in local mirror, to
2450                 * avoid sending a NewMessage Event. */
2451                /*TODO: We need to add the new 1.1 parameter as well:-) e.g. read*/
2452                Msg newMsg = new Msg(handle, folderId, 1); // TODO: Create define for read-state
2453                newMsg.transparent = (transparent == 1) ? true : false;
2454                if ( folderId == folderElement.getFolderByName(
2455                        BluetoothMapContract.FOLDER_NAME_OUTBOX).getFolderId() ) {
2456                    newMsg.localInitiatedSend = true;
2457                }
2458                getMsgListMsg().put(handle, newMsg);
2459            }
2460        } else { // type SMS_* of MMS
2461            for (BluetoothMapbMessage.vCard recipient : recipientList) {
2462                // Only send the message to the top level recipient
2463                if(recipient.getEnvLevel() == 0)
2464                {
2465                    /* Only send to first address */
2466                    String phone = recipient.getFirstPhoneNumber();
2467                    String email = recipient.getFirstEmail();
2468                    String folder = folderElement.getName();
2469                    boolean read = false;
2470                    boolean deliveryReport = true;
2471                    String msgBody = null;
2472
2473                    /* If MMS contains text only and the size is less than ten SMS's
2474                     * then convert the MMS to type SMS and then proceed
2475                     */
2476                    if (msg.getType().equals(TYPE.MMS) &&
2477                            (((BluetoothMapbMessageMime) msg).getTextOnly() == true)) {
2478                        msgBody = ((BluetoothMapbMessageMime) msg).getMessageAsText();
2479                        SmsManager smsMng = SmsManager.getDefault();
2480                        ArrayList<String> parts = smsMng.divideMessage(msgBody);
2481                        int smsParts = parts.size();
2482                        if (smsParts  <= CONVERT_MMS_TO_SMS_PART_COUNT ) {
2483                            if (D) Log.d(TAG, "pushMessage - converting MMS to SMS, sms parts="
2484                                    + smsParts );
2485                            msg.setType(mSmsType);
2486                        } else {
2487                            if (D) Log.d(TAG, "pushMessage - MMS text only but to big to " +
2488                                    "convert to SMS");
2489                            msgBody = null;
2490                        }
2491
2492                    }
2493
2494                    if (msg.getType().equals(TYPE.MMS)) {
2495                        /* Send message if folder is outbox else just store in draft*/
2496                        handle = sendMmsMessage(folder, phone, (BluetoothMapbMessageMime)msg,
2497                                transparent, retry);
2498                    } else if (msg.getType().equals(TYPE.SMS_GSM) ||
2499                            msg.getType().equals(TYPE.SMS_CDMA) ) {
2500                        /* Add the message to the database */
2501                        if(msgBody == null)
2502                            msgBody = ((BluetoothMapbMessageSms) msg).getSmsBody();
2503
2504                        /* We need to lock the SMS list while updating the database,
2505                         * to avoid sending events on MCE initiated operation. */
2506                        Uri contentUri = Uri.parse(Sms.CONTENT_URI+ "/" + folder);
2507                        Uri uri;
2508                        synchronized(getMsgListSms()) {
2509                            uri = Sms.addMessageToUri(mResolver, contentUri, phone, msgBody,
2510                                    "", System.currentTimeMillis(), read, deliveryReport);
2511
2512                            if(V) Log.v(TAG, "Sms.addMessageToUri() returned: " + uri);
2513                            if (uri == null) {
2514                                if (D) Log.d(TAG, "pushMessage - failure on add to uri "
2515                                        + contentUri);
2516                                return -1;
2517                            }
2518                            Cursor c = mResolver.query(uri, SMS_PROJECTION_SHORT, null, null, null);
2519
2520                            /* Extract the data for the inserted message, and store in local mirror,
2521                             * to avoid sending a NewMessage Event. */
2522                            try {
2523                                if (c != null && c.moveToFirst()) {
2524                                    long id = c.getLong(c.getColumnIndex(Sms._ID));
2525                                    int type = c.getInt(c.getColumnIndex(Sms.TYPE));
2526                                    int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
2527                                    int readFlag = c.getInt(c.getColumnIndex(Sms.READ));
2528                                    if(V) Log.v(TAG, "add message with id=" + id +
2529                                            " type=" + type + " threadId=" + threadId +
2530                                            " readFlag=" + readFlag + "to mMsgListSms");
2531                                    Msg newMsg = new Msg(id, type, threadId, readFlag);
2532                                    getMsgListSms().put(id, newMsg);
2533                                    c.close();
2534                                } else {
2535                                    Log.w(TAG,"Message: " + uri + " no longer exist!");
2536                                    /* This can only happen, if the message is deleted
2537                                     * just as it is added */
2538                                    return -1;
2539                                }
2540                            } finally {
2541                                if (c != null) c.close();
2542                            }
2543
2544                            handle = Long.parseLong(uri.getLastPathSegment());
2545
2546                            /* Send message if folder is outbox */
2547                            if (folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)) {
2548                                PushMsgInfo msgInfo = new PushMsgInfo(handle, transparent,
2549                                        retry, phone, uri);
2550                                mPushMsgList.put(handle, msgInfo);
2551                                sendMessage(msgInfo, msgBody);
2552                                if(V) Log.v(TAG, "sendMessage returned...");
2553                            } /* else just added to draft */
2554
2555                            /* sendMessage causes the message to be deleted and reinserted,
2556                             * hence we need to lock the list while this is happening. */
2557                        }
2558                    } else {
2559                        if (D) Log.d(TAG, "pushMessage - failure on type " );
2560                        return -1;
2561                    }
2562                }
2563            }
2564        }
2565
2566        /* If multiple recipients return handle of last */
2567        return handle;
2568    }
2569
2570    public long sendMmsMessage(String folder, String to_address, BluetoothMapbMessageMime msg,
2571            int transparent, int retry) {
2572        /*
2573         *strategy:
2574         *1) parse message into parts
2575         *if folder is outbox/drafts:
2576         *2) push message to draft
2577         *if folder is outbox:
2578         *3) move message to outbox (to trigger the mms app to add msg to pending_messages list)
2579         *4) send intent to mms app in order to wake it up.
2580         *else if folder !outbox:
2581         *1) push message to folder
2582         * */
2583        if (folder != null && (folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)
2584                ||  folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT))) {
2585            long handle = pushMmsToFolder(Mms.MESSAGE_BOX_DRAFTS, to_address, msg);
2586            /* if invalid handle (-1) then just return the handle
2587             * - else continue sending (if folder is outbox) */
2588            if (BluetoothMapAppParams.INVALID_VALUE_PARAMETER != handle &&
2589                    folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)) {
2590                Uri btMmsUri = MmsFileProvider.CONTENT_URI.buildUpon()
2591                        .appendPath(Long.toString(handle)).build();
2592                Intent sentIntent = new Intent(ACTION_MESSAGE_SENT);
2593                // TODO: update the mmsMsgList <- done in pushMmsToFolder() but check
2594                sentIntent.setType("message/" + Long.toString(handle));
2595                sentIntent.putExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.MMS.ordinal());
2596                sentIntent.putExtra(EXTRA_MESSAGE_SENT_HANDLE, handle); // needed for notification
2597                sentIntent.putExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, transparent);
2598                sentIntent.putExtra(EXTRA_MESSAGE_SENT_RETRY, retry);
2599                //sentIntent.setDataAndNormalize(btMmsUri);
2600                PendingIntent pendingSendIntent = PendingIntent.getBroadcast(mContext, 0,
2601                        sentIntent, 0);
2602                SmsManager.getDefault().sendMultimediaMessage(mContext,
2603                        btMmsUri, null/*locationUrl*/, null/*configOverrides*/,
2604                        pendingSendIntent);
2605            }
2606            return handle;
2607        } else {
2608            /* not allowed to push mms to anything but outbox/draft */
2609            throw  new IllegalArgumentException("Cannot push message to other " +
2610                    "folders than outbox/draft");
2611        }
2612    }
2613
2614    private void moveDraftToOutbox(long handle) {
2615        moveMmsToFolder(handle, mResolver, Mms.MESSAGE_BOX_OUTBOX);
2616    }
2617
2618    /**
2619     * Move a MMS to another folder.
2620     * @param handle the CP handle of the message to move
2621     * @param resolver the ContentResolver to use
2622     * @param folder the destination folder - use Mms.MESSAGE_BOX_xxx
2623     */
2624    private static void moveMmsToFolder(long handle, ContentResolver resolver, int folder) {
2625        /*Move message by changing the msg_box value in the content provider database */
2626        if (handle != -1) {
2627            String whereClause = " _id= " + handle;
2628            Uri uri = Mms.CONTENT_URI;
2629            Cursor queryResult = resolver.query(uri, null, whereClause, null, null);
2630            try {
2631                if (queryResult != null) {
2632                    if (queryResult.getCount() > 0) {
2633                        queryResult.moveToFirst();
2634                        ContentValues data = new ContentValues();
2635                        /* set folder to be outbox */
2636                        data.put(Mms.MESSAGE_BOX, folder);
2637                        resolver.update(uri, data, whereClause, null);
2638                        if (D) Log.d(TAG, "moved MMS message to " + getMmsFolderName(folder));
2639                    }
2640                } else {
2641                    Log.w(TAG, "Could not move MMS message to " + getMmsFolderName(folder));
2642                }
2643            } finally {
2644                if (queryResult != null) queryResult.close();
2645            }
2646        }
2647    }
2648    private long pushMmsToFolder(int folder, String to_address, BluetoothMapbMessageMime msg) {
2649        /**
2650         * strategy:
2651         * 1) parse msg into parts + header
2652         * 2) create thread id (abuse the ease of adding an SMS to get id for thread)
2653         * 3) push parts into content://mms/parts/ table
2654         * 3)
2655         */
2656
2657        ContentValues values = new ContentValues();
2658        values.put(Mms.MESSAGE_BOX, folder);
2659        values.put(Mms.READ, 0);
2660        values.put(Mms.SEEN, 0);
2661        if(msg.getSubject() != null) {
2662            values.put(Mms.SUBJECT, msg.getSubject());
2663        } else {
2664            values.put(Mms.SUBJECT, "");
2665        }
2666
2667        if(msg.getSubject() != null && msg.getSubject().length() > 0) {
2668            values.put(Mms.SUBJECT_CHARSET, 106);
2669        }
2670        values.put(Mms.CONTENT_TYPE, "application/vnd.wap.multipart.related");
2671        values.put(Mms.EXPIRY, 604800);
2672        values.put(Mms.MESSAGE_CLASS, PduHeaders.MESSAGE_CLASS_PERSONAL_STR);
2673        values.put(Mms.MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_SEND_REQ);
2674        values.put(Mms.MMS_VERSION, PduHeaders.CURRENT_MMS_VERSION);
2675        values.put(Mms.PRIORITY, PduHeaders.PRIORITY_NORMAL);
2676        values.put(Mms.READ_REPORT, PduHeaders.VALUE_NO);
2677        values.put(Mms.TRANSACTION_ID, "T"+ Long.toHexString(System.currentTimeMillis()));
2678        values.put(Mms.DELIVERY_REPORT, PduHeaders.VALUE_NO);
2679        values.put(Mms.LOCKED, 0);
2680        if(msg.getTextOnly() == true)
2681            values.put(Mms.TEXT_ONLY, true);
2682        values.put(Mms.MESSAGE_SIZE, msg.getSize());
2683
2684        // Get thread id
2685        Set<String> recipients = new HashSet<String>();
2686        recipients.addAll(Arrays.asList(to_address));
2687        values.put(Mms.THREAD_ID, Telephony.Threads.getOrCreateThreadId(mContext, recipients));
2688        Uri uri = Mms.CONTENT_URI;
2689
2690        synchronized (getMsgListMms()) {
2691
2692            uri = mResolver.insert(uri, values);
2693
2694            if (uri == null) {
2695                // unable to insert MMS
2696                Log.e(TAG, "Unabled to insert MMS " + values + "Uri: " + uri);
2697                return -1;
2698            }
2699            /* As we already have all the values we need, we could skip the query, but
2700               doing the query ensures we get any changes made by the content provider
2701               at insert. */
2702            Cursor c = mResolver.query(uri, MMS_PROJECTION_SHORT, null, null, null);
2703            try {
2704                if (c != null && c.moveToFirst()) {
2705                    long id = c.getLong(c.getColumnIndex(Mms._ID));
2706                    int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
2707                    int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
2708                    int readStatus = c.getInt(c.getColumnIndex(Mms.READ));
2709
2710                    /* We must filter out any actions made by the MCE. Add the new message to
2711                     * the list of known messages. */
2712
2713                    Msg newMsg = new Msg(id, type, threadId, readStatus);
2714                    newMsg.localInitiatedSend = true;
2715                    getMsgListMms().put(id, newMsg);
2716                    c.close();
2717                }
2718            } finally {
2719                if (c != null) c.close();
2720            }
2721        } // Done adding changes, unlock access to mMsgListMms to allow sending MMS events again
2722
2723        long handle = Long.parseLong(uri.getLastPathSegment());
2724        if (V) Log.v(TAG, " NEW URI " + uri.toString());
2725
2726        try {
2727            if(msg.getMimeParts() == null) {
2728                /* Perhaps this message have been deleted, and no longer have any content,
2729                 * but only headers */
2730                Log.w(TAG, "No MMS parts present...");
2731            } else {
2732                if(V) Log.v(TAG, "Adding " + msg.getMimeParts().size()
2733                        + " parts to the data base.");
2734                for(MimePart part : msg.getMimeParts()) {
2735                    int count = 0;
2736                    count++;
2737                    values.clear();
2738                    if(part.mContentType != null &&
2739                            part.mContentType.toUpperCase().contains("TEXT")) {
2740                        values.put(Mms.Part.CONTENT_TYPE, "text/plain");
2741                        values.put(Mms.Part.CHARSET, 106);
2742                        if(part.mPartName != null) {
2743                            values.put(Mms.Part.FILENAME, part.mPartName);
2744                            values.put(Mms.Part.NAME, part.mPartName);
2745                        } else {
2746                            values.put(Mms.Part.FILENAME, "text_" + count +".txt");
2747                            values.put(Mms.Part.NAME, "text_" + count +".txt");
2748                        }
2749                        // Ensure we have "ci" set
2750                        if(part.mContentId != null) {
2751                            values.put(Mms.Part.CONTENT_ID, part.mContentId);
2752                        } else {
2753                            if(part.mPartName != null) {
2754                                values.put(Mms.Part.CONTENT_ID, "<" + part.mPartName + ">");
2755                            } else {
2756                                values.put(Mms.Part.CONTENT_ID, "<text_" + count + ">");
2757                            }
2758                        }
2759                        // Ensure we have "cl" set
2760                        if(part.mContentLocation != null) {
2761                            values.put(Mms.Part.CONTENT_LOCATION, part.mContentLocation);
2762                        } else {
2763                            if(part.mPartName != null) {
2764                                values.put(Mms.Part.CONTENT_LOCATION, part.mPartName + ".txt");
2765                            } else {
2766                                values.put(Mms.Part.CONTENT_LOCATION, "text_" + count + ".txt");
2767                            }
2768                        }
2769
2770                        if(part.mContentDisposition != null) {
2771                            values.put(Mms.Part.CONTENT_DISPOSITION, part.mContentDisposition);
2772                        }
2773                        values.put(Mms.Part.TEXT, part.getDataAsString());
2774                        uri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/part");
2775                        uri = mResolver.insert(uri, values);
2776                        if(V) Log.v(TAG, "Added TEXT part");
2777
2778                    } else if (part.mContentType != null &&
2779                            part.mContentType.toUpperCase().contains("SMIL")){
2780                        values.put(Mms.Part.SEQ, -1);
2781                        values.put(Mms.Part.CONTENT_TYPE, "application/smil");
2782                        if(part.mContentId != null) {
2783                            values.put(Mms.Part.CONTENT_ID, part.mContentId);
2784                        } else {
2785                            values.put(Mms.Part.CONTENT_ID, "<smil_" + count + ">");
2786                        }
2787                        if(part.mContentLocation != null) {
2788                            values.put(Mms.Part.CONTENT_LOCATION, part.mContentLocation);
2789                        } else {
2790                            values.put(Mms.Part.CONTENT_LOCATION, "smil_" + count + ".xml");
2791                        }
2792
2793                        if(part.mContentDisposition != null)
2794                            values.put(Mms.Part.CONTENT_DISPOSITION, part.mContentDisposition);
2795                        values.put(Mms.Part.FILENAME, "smil.xml");
2796                        values.put(Mms.Part.NAME, "smil.xml");
2797                        values.put(Mms.Part.TEXT, new String(part.mData, "UTF-8"));
2798
2799                        uri = Uri.parse(Mms.CONTENT_URI+ "/" + handle + "/part");
2800                        uri = mResolver.insert(uri, values);
2801                        if (V) Log.v(TAG, "Added SMIL part");
2802
2803                    }else /*VIDEO/AUDIO/IMAGE*/ {
2804                        writeMmsDataPart(handle, part, count);
2805                        if (V) Log.v(TAG, "Added OTHER part");
2806                    }
2807                    if (uri != null){
2808                        if (V) Log.v(TAG, "Added part with content-type: " + part.mContentType
2809                                + " to Uri: " + uri.toString());
2810                    }
2811                }
2812            }
2813        } catch (UnsupportedEncodingException e) {
2814            Log.w(TAG, e);
2815        } catch (IOException e) {
2816            Log.w(TAG, e);
2817        }
2818
2819        values.clear();
2820        values.put(Mms.Addr.CONTACT_ID, "null");
2821        values.put(Mms.Addr.ADDRESS, "insert-address-token");
2822        values.put(Mms.Addr.TYPE, BluetoothMapContent.MMS_FROM);
2823        values.put(Mms.Addr.CHARSET, 106);
2824
2825        uri = Uri.parse(Mms.CONTENT_URI + "/"  + handle + "/addr");
2826        uri = mResolver.insert(uri, values);
2827        if (uri != null && V){
2828            Log.v(TAG, " NEW URI " + uri.toString());
2829        }
2830
2831        values.clear();
2832        values.put(Mms.Addr.CONTACT_ID, "null");
2833        values.put(Mms.Addr.ADDRESS, to_address);
2834        values.put(Mms.Addr.TYPE, BluetoothMapContent.MMS_TO);
2835        values.put(Mms.Addr.CHARSET, 106);
2836
2837        uri = Uri.parse(Mms.CONTENT_URI + "/"  + handle + "/addr");
2838        uri = mResolver.insert(uri, values);
2839        if (uri != null && V){
2840            Log.v(TAG, " NEW URI " + uri.toString());
2841        }
2842        return handle;
2843    }
2844
2845
2846    private void writeMmsDataPart(long handle, MimePart part, int count) throws IOException{
2847        ContentValues values = new ContentValues();
2848        values.put(Mms.Part.MSG_ID, handle);
2849        if(part.mContentType != null) {
2850            values.put(Mms.Part.CONTENT_TYPE, part.mContentType);
2851        } else {
2852            Log.w(TAG, "MMS has no CONTENT_TYPE for part " + count);
2853        }
2854        if(part.mContentId != null) {
2855            values.put(Mms.Part.CONTENT_ID, part.mContentId);
2856        } else {
2857            if(part.mPartName != null) {
2858                values.put(Mms.Part.CONTENT_ID, "<" + part.mPartName + ">");
2859            } else {
2860                values.put(Mms.Part.CONTENT_ID, "<part_" + count + ">");
2861            }
2862        }
2863
2864        if(part.mContentLocation != null) {
2865            values.put(Mms.Part.CONTENT_LOCATION, part.mContentLocation);
2866        } else {
2867            if(part.mPartName != null) {
2868                values.put(Mms.Part.CONTENT_LOCATION, part.mPartName + ".dat");
2869            } else {
2870                values.put(Mms.Part.CONTENT_LOCATION, "part_" + count + ".dat");
2871            }
2872        }
2873        if(part.mContentDisposition != null)
2874            values.put(Mms.Part.CONTENT_DISPOSITION, part.mContentDisposition);
2875        if(part.mPartName != null) {
2876            values.put(Mms.Part.FILENAME, part.mPartName);
2877            values.put(Mms.Part.NAME, part.mPartName);
2878        } else {
2879            /* We must set at least one part identifier */
2880            values.put(Mms.Part.FILENAME, "part_" + count + ".dat");
2881            values.put(Mms.Part.NAME, "part_" + count + ".dat");
2882        }
2883        Uri partUri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/part");
2884        Uri res = mResolver.insert(partUri, values);
2885
2886        // Add data to part
2887        OutputStream os = mResolver.openOutputStream(res);
2888        os.write(part.mData);
2889        os.close();
2890    }
2891
2892
2893    public void sendMessage(PushMsgInfo msgInfo, String msgBody) {
2894
2895        SmsManager smsMng = SmsManager.getDefault();
2896        ArrayList<String> parts = smsMng.divideMessage(msgBody);
2897        msgInfo.parts = parts.size();
2898        // We add a time stamp to differentiate delivery reports from each other for resent messages
2899        msgInfo.timestamp = Calendar.getInstance().getTime().getTime();
2900        msgInfo.partsDelivered = 0;
2901        msgInfo.partsSent = 0;
2902
2903        ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>(msgInfo.parts);
2904        ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(msgInfo.parts);
2905
2906        /*       We handle the SENT intent in the MAP service, as this object
2907         *       is destroyed at disconnect, hence if a disconnect occur while sending
2908         *       a message, there is no intent handler to move the message from outbox
2909         *       to the correct folder.
2910         *       The correct solution would be to create a service that will start based on
2911         *       the intent, if BT is turned off. */
2912
2913        for (int i = 0; i < msgInfo.parts; i++) {
2914            Intent intentDelivery, intentSent;
2915
2916            intentDelivery = new Intent(ACTION_MESSAGE_DELIVERY, null);
2917            /* Add msgId and part number to ensure the intents are different, and we
2918             * thereby get an intent for each msg part.
2919             * setType is needed to create different intents for each message id/ time stamp,
2920             * as the extras are not used when comparing. */
2921            intentDelivery.setType("message/" + Long.toString(msgInfo.id) + msgInfo.timestamp + i);
2922            intentDelivery.putExtra(EXTRA_MESSAGE_SENT_HANDLE, msgInfo.id);
2923            intentDelivery.putExtra(EXTRA_MESSAGE_SENT_TIMESTAMP, msgInfo.timestamp);
2924            PendingIntent pendingIntentDelivery = PendingIntent.getBroadcast(mContext, 0,
2925                    intentDelivery, PendingIntent.FLAG_UPDATE_CURRENT);
2926
2927            intentSent = new Intent(ACTION_MESSAGE_SENT, null);
2928            /* Add msgId and part number to ensure the intents are different, and we
2929             * thereby get an intent for each msg part.
2930             * setType is needed to create different intents for each message id/ time stamp,
2931             * as the extras are not used when comparing. */
2932            intentSent.setType("message/" + Long.toString(msgInfo.id) + msgInfo.timestamp + i);
2933            intentSent.putExtra(EXTRA_MESSAGE_SENT_HANDLE, msgInfo.id);
2934            intentSent.putExtra(EXTRA_MESSAGE_SENT_URI, msgInfo.uri.toString());
2935            intentSent.putExtra(EXTRA_MESSAGE_SENT_RETRY, msgInfo.retry);
2936            intentSent.putExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, msgInfo.transparent);
2937
2938            PendingIntent pendingIntentSent = PendingIntent.getBroadcast(mContext, 0,
2939                    intentSent, PendingIntent.FLAG_UPDATE_CURRENT);
2940
2941            // We use the same pending intent for all parts, but do not set the one shot flag.
2942            deliveryIntents.add(pendingIntentDelivery);
2943            sentIntents.add(pendingIntentSent);
2944        }
2945
2946        Log.d(TAG, "sendMessage to " + msgInfo.phone);
2947
2948        smsMng.sendMultipartTextMessage(msgInfo.phone, null, parts, sentIntents,
2949                deliveryIntents);
2950    }
2951
2952    private class SmsBroadcastReceiver extends BroadcastReceiver {
2953        private final String[] ID_PROJECTION = new String[] { Sms._ID };
2954        private final Uri UPDATE_STATUS_URI = Uri.withAppendedPath(Sms.CONTENT_URI, "/status");
2955
2956        public void register() {
2957            Handler handler = new Handler(Looper.getMainLooper());
2958
2959            IntentFilter intentFilter = new IntentFilter();
2960            intentFilter.addAction(ACTION_MESSAGE_DELIVERY);
2961            /* The reception of ACTION_MESSAGE_SENT have been moved to the MAP
2962             * service, to be able to handle message sent events after a disconnect. */
2963            //intentFilter.addAction(ACTION_MESSAGE_SENT);
2964            try{
2965                intentFilter.addDataType("message/*");
2966            } catch (MalformedMimeTypeException e) {
2967                Log.e(TAG, "Wrong mime type!!!", e);
2968            }
2969
2970            mContext.registerReceiver(this, intentFilter, null, handler);
2971        }
2972
2973        public void unregister() {
2974            try {
2975                mContext.unregisterReceiver(this);
2976            } catch (IllegalArgumentException e) {
2977                /* do nothing */
2978            }
2979        }
2980
2981        @Override
2982        public void onReceive(Context context, Intent intent) {
2983            String action = intent.getAction();
2984            long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1);
2985            PushMsgInfo msgInfo = mPushMsgList.get(handle);
2986
2987            Log.d(TAG, "onReceive: action"  + action);
2988
2989            if (msgInfo == null) {
2990                Log.d(TAG, "onReceive: no msgInfo found for handle " + handle);
2991                return;
2992            }
2993
2994            if (action.equals(ACTION_MESSAGE_SENT)) {
2995                int result = intent.getIntExtra(EXTRA_MESSAGE_SENT_RESULT,
2996                        Activity.RESULT_CANCELED);
2997                msgInfo.partsSent++;
2998                if(result != Activity.RESULT_OK) {
2999                    /* If just one of the parts in the message fails, we need to send the
3000                     * entire message again
3001                     */
3002                    msgInfo.failedSent = true;
3003                }
3004                if(D) Log.d(TAG, "onReceive: msgInfo.partsSent = " + msgInfo.partsSent
3005                        + ", msgInfo.parts = " + msgInfo.parts + " result = " + result);
3006
3007                if (msgInfo.partsSent == msgInfo.parts) {
3008                    actionMessageSent(context, intent, msgInfo);
3009                }
3010            } else if (action.equals(ACTION_MESSAGE_DELIVERY)) {
3011                long timestamp = intent.getLongExtra(EXTRA_MESSAGE_SENT_TIMESTAMP, 0);
3012                int status = -1;
3013                if(msgInfo.timestamp == timestamp) {
3014                    msgInfo.partsDelivered++;
3015                    byte[] pdu = intent.getByteArrayExtra("pdu");
3016                    String format = intent.getStringExtra("format");
3017
3018                    SmsMessage message = SmsMessage.createFromPdu(pdu, format);
3019                    if (message == null) {
3020                        Log.d(TAG, "actionMessageDelivery: Can't get message from pdu");
3021                        return;
3022                    }
3023                    status = message.getStatus();
3024                    if(status != 0/*0 is success*/) {
3025                        msgInfo.statusDelivered = status;
3026                        if(D) Log.d(TAG, "msgInfo.statusDelivered = " + status);
3027                    }
3028                }
3029                if (msgInfo.partsDelivered == msgInfo.parts) {
3030                    actionMessageDelivery(context, intent, msgInfo);
3031                }
3032            } else {
3033                Log.d(TAG, "onReceive: Unknown action " + action);
3034            }
3035        }
3036
3037        private void actionMessageSent(Context context, Intent intent, PushMsgInfo msgInfo) {
3038            /* As the MESSAGE_SENT intent is forwarded from the MAP service, we use the intent
3039             * to carry the result, as getResult() will not return the correct value.
3040             */
3041            boolean delete = false;
3042
3043            if(D) Log.d(TAG,"actionMessageSent(): msgInfo.failedSent = " + msgInfo.failedSent);
3044
3045            msgInfo.sendInProgress = false;
3046
3047            if (msgInfo.failedSent == false) {
3048                if(D) Log.d(TAG, "actionMessageSent: result OK");
3049                if (msgInfo.transparent == 0) {
3050                    if (!Sms.moveMessageToFolder(context, msgInfo.uri,
3051                            Sms.MESSAGE_TYPE_SENT, 0)) {
3052                        Log.w(TAG, "Failed to move " + msgInfo.uri + " to SENT");
3053                    }
3054                } else {
3055                    delete = true;
3056                }
3057
3058                Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msgInfo.id,
3059                        getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType);
3060                sendEvent(evt);
3061
3062            } else {
3063                if (msgInfo.retry == 1) {
3064                    /* Notify failure, but keep message in outbox for resending */
3065                    msgInfo.resend = true;
3066                    msgInfo.partsSent = 0; // Reset counter for the retry
3067                    msgInfo.failedSent = false;
3068                    Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, msgInfo.id,
3069                            getSmsFolderName(Sms.MESSAGE_TYPE_OUTBOX), null, mSmsType);
3070                    sendEvent(evt);
3071                } else {
3072                    if (msgInfo.transparent == 0) {
3073                        if (!Sms.moveMessageToFolder(context, msgInfo.uri,
3074                                Sms.MESSAGE_TYPE_FAILED, 0)) {
3075                            Log.w(TAG, "Failed to move " + msgInfo.uri + " to FAILED");
3076                        }
3077                    } else {
3078                        delete = true;
3079                    }
3080
3081                    Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, msgInfo.id,
3082                            getSmsFolderName(Sms.MESSAGE_TYPE_FAILED), null, mSmsType);
3083                    sendEvent(evt);
3084                }
3085            }
3086
3087            if (delete == true) {
3088                /* Delete from Observer message list to avoid delete notifications */
3089                synchronized(getMsgListSms()) {
3090                    getMsgListSms().remove(msgInfo.id);
3091                }
3092
3093                /* Delete from DB */
3094                mResolver.delete(msgInfo.uri, null, null);
3095            }
3096        }
3097
3098        private void actionMessageDelivery(Context context, Intent intent, PushMsgInfo msgInfo) {
3099            Uri messageUri = intent.getData();
3100            msgInfo.sendInProgress = false;
3101
3102            Cursor cursor = mResolver.query(msgInfo.uri, ID_PROJECTION, null, null, null);
3103
3104            try {
3105                if (cursor.moveToFirst()) {
3106                    int messageId = cursor.getInt(0);
3107
3108                    Uri updateUri = ContentUris.withAppendedId(UPDATE_STATUS_URI, messageId);
3109
3110                    if(D) Log.d(TAG, "actionMessageDelivery: uri=" + messageUri + ", status="
3111                            + msgInfo.statusDelivered);
3112
3113                    ContentValues contentValues = new ContentValues(2);
3114
3115                    contentValues.put(Sms.STATUS, msgInfo.statusDelivered);
3116                    contentValues.put(Inbox.DATE_SENT, System.currentTimeMillis());
3117                    mResolver.update(updateUri, contentValues, null, null);
3118                } else {
3119                    Log.d(TAG, "Can't find message for status update: " + messageUri);
3120                }
3121            } finally {
3122                if (cursor != null) cursor.close();
3123            }
3124
3125            if (msgInfo.statusDelivered == 0) {
3126                Event evt = new Event(EVENT_TYPE_DELEVERY_SUCCESS, msgInfo.id,
3127                        getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType);
3128                sendEvent(evt);
3129            } else {
3130                Event evt = new Event(EVENT_TYPE_DELIVERY_FAILURE, msgInfo.id,
3131                        getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType);
3132                sendEvent(evt);
3133            }
3134
3135            mPushMsgList.remove(msgInfo.id);
3136        }
3137    }
3138
3139    /**
3140     * Handle MMS sent intents in disconnected(MNS) state, where we do not need to send any
3141     * notifications.
3142     * @param context The context to use for provider operations
3143     * @param intent The intent received
3144     * @param result The result
3145     */
3146    static public void actionMmsSent(Context context, Intent intent, int result,
3147            Map<Long, Msg> mmsMsgList) {
3148        /*
3149         * if transparent:
3150         *   delete message and send notification(regardless of result)
3151         * else
3152         *   Result == Success:
3153         *     move to sent folder (will trigger notification)
3154         *   Result == Fail:
3155         *     move to outbox (send delivery fail notification)
3156         */
3157        if(D) Log.d(TAG,"actionMmsSent()");
3158        int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
3159        long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1);
3160        if(handle < 0) {
3161            Log.w(TAG, "Intent received for an invalid handle");
3162            return;
3163        }
3164        ContentResolver resolver = context.getContentResolver();
3165        if(transparent == 1) {
3166            /* The specification is a bit unclear about the transparent flag. If it is set
3167             * no copy of the message shall be kept in the send folder after the message
3168             * was send, but in the case of a send error, it is unclear what to do.
3169             * As it will not be transparent if we keep the message in any folder,
3170             * we delete the message regardless of the result.
3171             * If we however do have a MNS connection we need to send a notification. */
3172            Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
3173            /* Delete from observer message list to avoid delete notifications */
3174            if(mmsMsgList != null) {
3175                synchronized(mmsMsgList) {
3176                    mmsMsgList.remove(handle);
3177                }
3178            }
3179            /* Delete message */
3180            if(D) Log.d(TAG,"Transparent in use - delete");
3181            resolver.delete(uri, null, null);
3182        } else if (result == Activity.RESULT_OK) {
3183            /* This will trigger a notification */
3184            moveMmsToFolder(handle, resolver, Mms.MESSAGE_BOX_SENT);
3185        } else {
3186            if(mmsMsgList != null) {
3187                synchronized(mmsMsgList) {
3188                    Msg msg = mmsMsgList.get(handle);
3189                    if(msg != null) {
3190                    msg.type=Mms.MESSAGE_BOX_OUTBOX;
3191                    }
3192                }
3193            }
3194            /* Hand further retries over to the MMS application */
3195            moveMmsToFolder(handle, resolver, Mms.MESSAGE_BOX_OUTBOX);
3196        }
3197    }
3198
3199    static public void actionMessageSentDisconnected(Context context, Intent intent, int result) {
3200        TYPE type = TYPE.fromOrdinal(
3201        intent.getIntExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal()));
3202        if(type == TYPE.MMS) {
3203        actionMmsSent(context, intent, result, null);
3204        } else {
3205        actionSmsSentDisconnected(context, intent, result);
3206        }
3207    }
3208
3209    static public void actionSmsSentDisconnected(Context context, Intent intent, int result) {
3210        boolean delete = false;
3211        //int retry = intent.getIntExtra(EXTRA_MESSAGE_SENT_RETRY, 0);
3212        int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
3213        String uriString = intent.getStringExtra(EXTRA_MESSAGE_SENT_URI);
3214        if(uriString == null) {
3215            // Nothing we can do about it, just bail out
3216            return;
3217        }
3218        Uri uri = Uri.parse(uriString);
3219
3220        if (result == Activity.RESULT_OK) {
3221            Log.d(TAG, "actionMessageSentDisconnected: result OK");
3222            if (transparent == 0) {
3223                if (!Sms.moveMessageToFolder(context, uri,
3224                        Sms.MESSAGE_TYPE_SENT, 0)) {
3225                    Log.d(TAG, "Failed to move " + uri + " to SENT");
3226                }
3227            } else {
3228                delete = true;
3229            }
3230        } else {
3231            /*if (retry == 1) {
3232                 The retry feature only works while connected, else we fail the send,
3233             * and move the message to failed, to let the user/app resend manually later.
3234            } else */{
3235                if (transparent == 0) {
3236                    if (!Sms.moveMessageToFolder(context, uri,
3237                            Sms.MESSAGE_TYPE_FAILED, 0)) {
3238                        Log.d(TAG, "Failed to move " + uri + " to FAILED");
3239                    }
3240                } else {
3241                    delete = true;
3242                }
3243            }
3244        }
3245
3246        if (delete == true) {
3247            /* Delete from DB */
3248            ContentResolver resolver = context.getContentResolver();
3249            if(resolver != null) {
3250                resolver.delete(uri, null, null);
3251            } else {
3252                Log.w(TAG, "Unable to get resolver");
3253            }
3254        }
3255    }
3256
3257    private void registerPhoneServiceStateListener() {
3258        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(
3259                Context.TELEPHONY_SERVICE);
3260        tm.listen(mPhoneListener, PhoneStateListener.LISTEN_SERVICE_STATE);
3261    }
3262
3263    private void unRegisterPhoneServiceStateListener() {
3264        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(
3265                Context.TELEPHONY_SERVICE);
3266        tm.listen(mPhoneListener, PhoneStateListener.LISTEN_NONE);
3267    }
3268
3269    private void resendPendingMessages() {
3270        /* Send pending messages in outbox */
3271        String where = "type = " + Sms.MESSAGE_TYPE_OUTBOX;
3272        Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
3273                null);
3274        try {
3275            if (c != null && c.moveToFirst()) {
3276                do {
3277                    long id = c.getLong(c.getColumnIndex(Sms._ID));
3278                    String msgBody = c.getString(c.getColumnIndex(Sms.BODY));
3279                    PushMsgInfo msgInfo = mPushMsgList.get(id);
3280                    if (msgInfo == null || msgInfo.resend == false ||
3281                            msgInfo.sendInProgress == true) {
3282                        continue;
3283                    }
3284                    msgInfo.sendInProgress = true;
3285                    sendMessage(msgInfo, msgBody);
3286                } while (c.moveToNext());
3287            }
3288        } finally {
3289            if (c != null) c.close();
3290        }
3291
3292
3293    }
3294
3295    private void failPendingMessages() {
3296        /* Move pending messages from outbox to failed */
3297        String where = "type = " + Sms.MESSAGE_TYPE_OUTBOX;
3298        Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
3299                null);
3300        try {
3301            if (c != null && c.moveToFirst()) {
3302                do {
3303                    long id = c.getLong(c.getColumnIndex(Sms._ID));
3304                    String msgBody = c.getString(c.getColumnIndex(Sms.BODY));
3305                    PushMsgInfo msgInfo = mPushMsgList.get(id);
3306                    if (msgInfo == null || msgInfo.resend == false) {
3307                        continue;
3308                    }
3309                    Sms.moveMessageToFolder(mContext, msgInfo.uri,
3310                            Sms.MESSAGE_TYPE_FAILED, 0);
3311                } while (c.moveToNext());
3312            }
3313        } finally {
3314            if (c != null) c.close();
3315        }
3316
3317    }
3318
3319    private void removeDeletedMessages() {
3320        /* Remove messages from virtual "deleted" folder (thread_id -1) */
3321        mResolver.delete(Sms.CONTENT_URI,
3322                "thread_id = " + DELETED_THREAD_ID, null);
3323    }
3324
3325    private PhoneStateListener mPhoneListener = new PhoneStateListener() {
3326        @Override
3327        public void onServiceStateChanged(ServiceState serviceState) {
3328            Log.d(TAG, "Phone service state change: " + serviceState.getState());
3329            if (serviceState.getState() == ServiceState.STATE_IN_SERVICE) {
3330                resendPendingMessages();
3331            }
3332        }
3333    };
3334
3335    public void init() {
3336        mSmsBroadcastReceiver.register();
3337        registerPhoneServiceStateListener();
3338        mInitialized = true;
3339    }
3340
3341    public void deinit() {
3342        mInitialized = false;
3343        unregisterObserver();
3344        mSmsBroadcastReceiver.unregister();
3345        unRegisterPhoneServiceStateListener();
3346        failPendingMessages();
3347        removeDeletedMessages();
3348    }
3349
3350    public boolean handleSmsSendIntent(Context context, Intent intent){
3351        TYPE type = TYPE.fromOrdinal(
3352            intent.getIntExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal()));
3353        if(type == TYPE.MMS) {
3354            return handleMmsSendIntent(context, intent);
3355        } else {
3356            if(mInitialized) {
3357                mSmsBroadcastReceiver.onReceive(context, intent);
3358                return true;
3359            }
3360        }
3361        return false;
3362    }
3363
3364    public boolean handleMmsSendIntent(Context context, Intent intent){
3365        if(D) Log.w(TAG, "handleMmsSendIntent()");
3366        if(mMnsClient.isConnected() == false) {
3367            // No need to handle notifications, just use default handling
3368            if(D) Log.w(TAG, "MNS not connected - use static handling");
3369            return false;
3370        }
3371        long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1);
3372        int result = intent.getIntExtra(EXTRA_MESSAGE_SENT_RESULT, Activity.RESULT_CANCELED);
3373        actionMmsSent(context, intent, result, getMsgListMms());
3374        if(handle < 0) {
3375            Log.w(TAG, "Intent received for an invalid handle");
3376            return true;
3377        }
3378        if(result != Activity.RESULT_OK) {
3379            if(mObserverRegistered) {
3380                Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, handle,
3381                        getMmsFolderName(Mms.MESSAGE_BOX_OUTBOX), null, TYPE.MMS);
3382                sendEvent(evt);
3383            }
3384        } else {
3385            int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
3386            if(transparent != 0) {
3387                if(mObserverRegistered) {
3388                    Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, handle,
3389                            getMmsFolderName(Mms.MESSAGE_BOX_OUTBOX), null, TYPE.MMS);
3390                    sendEvent(evt);
3391                }
3392            }
3393        }
3394        return true;
3395    }
3396
3397}
3398