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