BluetoothMapContentObserver.java revision 2fbb1d97d08d5d72fe824e543c714e56cd7be236
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.SmsMessage;
51import android.telephony.TelephonyManager;
52import android.text.format.DateUtils;
53import android.text.TextUtils;
54import android.util.Log;
55import android.util.Xml;
56
57import org.xmlpull.v1.XmlSerializer;
58
59import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
60import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart;
61import com.android.bluetooth.mapapi.BluetoothMapContract;
62import com.android.bluetooth.mapapi.BluetoothMapContract.MessageColumns;
63import com.google.android.mms.pdu.PduHeaders;
64
65import java.io.FileNotFoundException;
66import java.io.FileOutputStream;
67import java.io.IOException;
68import java.io.OutputStream;
69import java.io.StringWriter;
70import java.io.UnsupportedEncodingException;
71import java.util.ArrayList;
72import java.util.Arrays;
73import java.util.Calendar;
74import java.util.Collections;
75import java.util.HashMap;
76import java.util.HashSet;
77import java.util.Map;
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        public 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        public 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        public 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        public 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        public 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        public 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        public 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(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(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(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(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(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(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(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(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(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(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(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(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                                            || currentPresenceStatus != presenceStatus) {
1984                                        long lastOnline = c
1985                                                .getLong(cInfo.mContactColLastOnline);
1986                                        contact.setPresenceAvailability(presenceState);
1987                                        contact.setLastActivity(lastOnline);
1988                                        if (currentPresenceStatus != null
1989                                                && !currentPresenceStatus
1990                                                .equals(presenceStatus)) {
1991                                            contact.setPresenceStatus(presenceStatus);
1992                                        }
1993                                        Event evt = new Event(
1994                                                EVENT_TYPE_PRESENCE,
1995                                                uci,
1996                                                mAccount.getType(),
1997                                                contact.getName(),
1998                                                String.valueOf(contact
1999                                                        .getPriority()),
2000                                                        BluetoothMapUtils
2001                                                        .getDateTimeString(lastOnline),
2002                                                        convoId, convoName,
2003                                                        presenceState, presenceStatus,
2004                                                        0);
2005                                        sendEvent(evt);
2006                                    }
2007
2008                                    // Check if chat state is updated
2009                                    int chatState = c.getInt(cInfo.mContactColChatState);
2010                                    if (contact.getChatState() != chatState) {
2011                                        // Get DB timestamp
2012                                        long lastActivity = c.getLong(cInfo.mContactColLastActive);
2013                                        contact.setLastActivity(lastActivity);
2014                                        contact.setChatState(chatState);
2015                                        Event evt = new Event(
2016                                                EVENT_TYPE_CHAT_STATE,
2017                                                uci,
2018                                                mAccount.getType(),
2019                                                contact.getName(),
2020                                                String.valueOf(contact
2021                                                        .getPriority()),
2022                                                        BluetoothMapUtils
2023                                                        .getDateTimeString(lastActivity),
2024                                                        convoId, convoName, 0, null,
2025                                                        chatState);
2026                                        sendEvent(evt);
2027                                    }
2028                                    contactList.put(uci, contact);
2029                                }
2030                            } while (c.moveToNext());
2031                        }
2032                        if(getContactList().size() > 0) {
2033                            // one or more contacts were deleted, hence the conversation listing
2034                            // version counter should change.
2035                            listChanged = true;
2036                        }
2037                        setContactList(contactList, listChanged);
2038                    } // end synchronized
2039                } finally {
2040                    if (c != null) c.close();
2041                }
2042            } catch (RemoteException e) {
2043                mMasInstance.restartObexServerSession();
2044                Log.w(TAG,
2045                        "Problems contacting the ContentProvider in mas Instance "
2046                                + mMasId + " restaring ObexServerSession");
2047            }
2048
2049        }
2050        // TODO: conversation contact updates if IM and SMS(MMS in one instance
2051    }
2052
2053    private boolean setEmailMessageStatusDelete(BluetoothMapFolderElement mCurrentFolder,
2054            String uriStr, long handle, int status) {
2055        boolean res = false;
2056        Uri uri = Uri.parse(uriStr + BluetoothMapContract.TABLE_MESSAGE);
2057
2058        int updateCount = 0;
2059        ContentValues contentValues = new ContentValues();
2060        BluetoothMapFolderElement deleteFolder = mFolders.
2061                getFolderByName(BluetoothMapContract.FOLDER_NAME_DELETED);
2062        contentValues.put(BluetoothMapContract.MessageColumns._ID, handle);
2063        synchronized(getMsgListMsg()) {
2064            Msg msg = getMsgListMsg().get(handle);
2065            if (status == BluetoothMapAppParams.STATUS_VALUE_YES) {
2066                /* Set deleted folder id */
2067                long folderId = -1;
2068                if(deleteFolder != null) {
2069                    folderId = deleteFolder.getFolderId();
2070                }
2071                contentValues.put(BluetoothMapContract.MessageColumns.FOLDER_ID,folderId);
2072                updateCount = mResolver.update(uri, contentValues, null, null);
2073                /* The race between updating the value in our cached values and the database
2074                 * is handled by the synchronized statement. */
2075                if(updateCount > 0) {
2076                    res = true;
2077                    if (msg != null) {
2078                        msg.oldFolderId = msg.folderId;
2079                        /* Update the folder ID to avoid triggering an event for MCE
2080                         * initiated actions. */
2081                        msg.folderId = folderId;
2082                    }
2083                    if(D) Log.d(TAG, "Deleted MSG: " + handle + " from folderId: " + folderId);
2084                } else {
2085                    Log.w(TAG, "Msg: " + handle + " - Set delete status " + status
2086                            + " failed for folderId " + folderId);
2087                }
2088            } else if (status == BluetoothMapAppParams.STATUS_VALUE_NO) {
2089                /* Undelete message. move to old folder if we know it,
2090                 * else move to inbox - as dictated by the spec. */
2091                if(msg != null && deleteFolder != null &&
2092                        msg.folderId == deleteFolder.getFolderId()) {
2093                    /* Only modify messages in the 'Deleted' folder */
2094                    long folderId = -1;
2095                    BluetoothMapFolderElement inboxFolder = mCurrentFolder.
2096                            getFolderByName(BluetoothMapContract.FOLDER_NAME_INBOX);
2097                    if (msg != null && msg.oldFolderId != -1) {
2098                        folderId = msg.oldFolderId;
2099                    } else {
2100                        if(inboxFolder != null) {
2101                            folderId = inboxFolder.getFolderId();
2102                        }
2103                        if(D)Log.d(TAG,"We did not delete the message, hence the old folder " +
2104                                "is unknown. Moving to inbox.");
2105                    }
2106                    contentValues.put(BluetoothMapContract.MessageColumns.FOLDER_ID, folderId);
2107                    updateCount = mResolver.update(uri, contentValues, null, null);
2108                    if(updateCount > 0) {
2109                        res = true;
2110                        /* Update the folder ID to avoid triggering an event for MCE
2111                         * initiated actions. */
2112                        /* UPDATE: Actually the BT-Spec. states that an undelete is a move of the
2113                         * message to INBOX - clearified in errata 5591.
2114                         * Therefore we update the cache to INBOX-folderId - to trigger a message
2115                         * shift event to the old-folder. */
2116                        if(inboxFolder != null) {
2117                            msg.folderId = inboxFolder.getFolderId();
2118                        } else {
2119                            msg.folderId = folderId;
2120                        }
2121                    } else {
2122                        if(D)Log.d(TAG,"We did not delete the message, hence the old folder " +
2123                                "is unknown. Moving to inbox.");
2124                    }
2125                }
2126            }
2127            if(V) {
2128                BluetoothMapFolderElement folderElement;
2129                String folderName = "unknown";
2130                if (msg != null) {
2131                    folderElement = mCurrentFolder.getFolderById(msg.folderId);
2132                    if(folderElement != null) {
2133                        folderName = folderElement.getName();
2134                    }
2135                }
2136                Log.d(TAG,"setEmailMessageStatusDelete: " + handle + " from " + folderName
2137                        + " status: " + status);
2138            }
2139        }
2140        if(!res) {
2141            Log.w(TAG, "Set delete status " + status + " failed.");
2142        }
2143        return res;
2144    }
2145
2146    private void updateThreadId(Uri uri, String valueString, long threadId) {
2147        ContentValues contentValues = new ContentValues();
2148        contentValues.put(valueString, threadId);
2149        mResolver.update(uri, contentValues, null, null);
2150    }
2151
2152    private boolean deleteMessageMms(long handle) {
2153        boolean res = false;
2154        Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
2155        Cursor c = mResolver.query(uri, null, null, null, null);
2156        try {
2157            if (c != null && c.moveToFirst()) {
2158                /* Move to deleted folder, or delete if already in deleted folder */
2159                int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
2160                if (threadId != DELETED_THREAD_ID) {
2161                    /* Set deleted thread id */
2162                    synchronized(getMsgListMms()) {
2163                        Msg msg = getMsgListMms().get(handle);
2164                        if(msg != null) { // This will always be the case
2165                            msg.threadId = DELETED_THREAD_ID;
2166                        }
2167                    }
2168                    updateThreadId(uri, Mms.THREAD_ID, DELETED_THREAD_ID);
2169                } else {
2170                    /* Delete from observer message list to avoid delete notifications */
2171                    synchronized(getMsgListMms()) {
2172                        getMsgListMms().remove(handle);
2173                    }
2174                    /* Delete message */
2175                    mResolver.delete(uri, null, null);
2176                }
2177                res = true;
2178            }
2179        } finally {
2180            if (c != null) c.close();
2181        }
2182
2183        return res;
2184    }
2185
2186    private boolean unDeleteMessageMms(long handle) {
2187        boolean res = false;
2188        Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
2189        Cursor c = mResolver.query(uri, null, null, null, null);
2190        try {
2191            if (c != null && c.moveToFirst()) {
2192                int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
2193                if (threadId == DELETED_THREAD_ID) {
2194                    /* Restore thread id from address, or if no thread for address
2195                     * create new thread by insert and remove of fake message */
2196                    String address;
2197                    long id = c.getLong(c.getColumnIndex(Mms._ID));
2198                    int msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
2199                    if (msgBox == Mms.MESSAGE_BOX_INBOX) {
2200                        address = BluetoothMapContent.getAddressMms(mResolver, id,
2201                                BluetoothMapContent.MMS_FROM);
2202                    } else {
2203                        address = BluetoothMapContent.getAddressMms(mResolver, id,
2204                                BluetoothMapContent.MMS_TO);
2205                    }
2206                    Set<String> recipients = new HashSet<String>();
2207                    recipients.addAll(Arrays.asList(address));
2208                    Long oldThreadId = Telephony.Threads.getOrCreateThreadId(mContext, recipients);
2209                    synchronized(getMsgListMms()) {
2210                        Msg msg = getMsgListMms().get(handle);
2211                        if(msg != null) { // This will always be the case
2212                            msg.threadId = oldThreadId.intValue();
2213                            // Spec. states that undelete shall shift the message to Inbox.
2214                            // Hence we need to trigger a message shift from INBOX to old-folder
2215                            // after undelete.
2216                            // We do this by changing the cached folder value to being inbox - hence
2217                            // the event handler will se the update as the message have been shifted
2218                            // from INBOX to old-folder. (Errata 5591 clearifies this)
2219                            msg.type = Mms.MESSAGE_BOX_INBOX;
2220                        }
2221                    }
2222                    updateThreadId(uri, Mms.THREAD_ID, oldThreadId);
2223                } else {
2224                    Log.d(TAG, "Message not in deleted folder: handle " + handle
2225                            + " threadId " + threadId);
2226                }
2227                res = true;
2228            }
2229        } finally {
2230            if (c != null) c.close();
2231        }
2232        return res;
2233    }
2234
2235    private boolean deleteMessageSms(long handle) {
2236        boolean res = false;
2237        Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle);
2238        Cursor c = mResolver.query(uri, null, null, null, null);
2239        try {
2240            if (c != null && c.moveToFirst()) {
2241                /* Move to deleted folder, or delete if already in deleted folder */
2242                int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
2243                if (threadId != DELETED_THREAD_ID) {
2244                    synchronized(getMsgListSms()) {
2245                        Msg msg = getMsgListSms().get(handle);
2246                        if(msg != null) { // This will always be the case
2247                            msg.threadId = DELETED_THREAD_ID;
2248                        }
2249                    }
2250                    /* Set deleted thread id */
2251                    updateThreadId(uri, Sms.THREAD_ID, DELETED_THREAD_ID);
2252                } else {
2253                    /* Delete from observer message list to avoid delete notifications */
2254                    synchronized(getMsgListSms()) {
2255                        getMsgListSms().remove(handle);
2256                    }
2257                    /* Delete message */
2258                    mResolver.delete(uri, null, null);
2259                }
2260                res = true;
2261            }
2262        } finally {
2263            if (c != null) c.close();
2264        }
2265        return res;
2266    }
2267
2268    private boolean unDeleteMessageSms(long handle) {
2269        boolean res = false;
2270        Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle);
2271        Cursor c = mResolver.query(uri, null, null, null, null);
2272        try {
2273            if (c != null && c.moveToFirst()) {
2274                int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
2275                if (threadId == DELETED_THREAD_ID) {
2276                    String address = c.getString(c.getColumnIndex(Sms.ADDRESS));
2277                    Set<String> recipients = new HashSet<String>();
2278                    recipients.addAll(Arrays.asList(address));
2279                    Long oldThreadId = Telephony.Threads.getOrCreateThreadId(mContext, recipients);
2280                    synchronized(getMsgListSms()) {
2281                        Msg msg = getMsgListSms().get(handle);
2282                        if(msg != null) {
2283                            msg.threadId = oldThreadId.intValue();
2284                            /* This will always be the case
2285                             * The threadId is specified as an int, so it is safe to truncate
2286                             * TODO: Test that this will trigger a message-shift from Inbox
2287                             * to old-folder
2288                             **/
2289                            /* Spec. states that undelete shall shift the message to Inbox.
2290                             * Hence we need to trigger a message shift from INBOX to old-folder
2291                             * after undelete.
2292                             * We do this by changing the cached folder value to being inbox - hence
2293                             * the event handler will se the update as the message have been shifted
2294                             * from INBOX to old-folder. (Errata 5591 clearifies this)
2295                             * */
2296                            msg.type = Sms.MESSAGE_TYPE_INBOX;
2297                        }
2298                    }
2299                    updateThreadId(uri, Sms.THREAD_ID, oldThreadId);
2300                } else {
2301                    Log.d(TAG, "Message not in deleted folder: handle " + handle
2302                            + " threadId " + threadId);
2303                }
2304                res = true;
2305            }
2306        } finally {
2307            if (c != null) c.close();
2308        }
2309        return res;
2310    }
2311
2312    /**
2313     *
2314     * @param handle
2315     * @param type
2316     * @param mCurrentFolder
2317     * @param uriStr
2318     * @param statusValue
2319     * @return true is success
2320     */
2321    public boolean setMessageStatusDeleted(long handle, TYPE type,
2322            BluetoothMapFolderElement mCurrentFolder, String uriStr, int statusValue) {
2323        boolean res = false;
2324        if (D) Log.d(TAG, "setMessageStatusDeleted: handle " + handle
2325                + " type " + type + " value " + statusValue);
2326
2327        if (type == TYPE.EMAIL) {
2328            res = setEmailMessageStatusDelete(mCurrentFolder, uriStr, handle, statusValue);
2329        } else if (type == TYPE.IM) {
2330            // TODO: to do when deleting IM message
2331            if (D) Log.d(TAG, "setMessageStatusDeleted: IM not handled" );
2332        } else {
2333            if (statusValue == BluetoothMapAppParams.STATUS_VALUE_YES) {
2334                if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) {
2335                    res = deleteMessageSms(handle);
2336                } else if (type == TYPE.MMS) {
2337                    res = deleteMessageMms(handle);
2338                }
2339            } else if (statusValue == BluetoothMapAppParams.STATUS_VALUE_NO) {
2340                if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) {
2341                    res = unDeleteMessageSms(handle);
2342                } else if (type == TYPE.MMS) {
2343                    res = unDeleteMessageMms(handle);
2344                }
2345            }
2346        }
2347        return res;
2348    }
2349
2350    /**
2351     *
2352     * @param handle
2353     * @param type
2354     * @param uriStr
2355     * @param statusValue
2356     * @return true at success
2357     */
2358    public boolean setMessageStatusRead(long handle, TYPE type, String uriStr, int statusValue)
2359            throws RemoteException{
2360        int count = 0;
2361
2362        if (D) Log.d(TAG, "setMessageStatusRead: handle " + handle
2363                + " type " + type + " value " + statusValue);
2364
2365        /* Approved MAP spec errata 3445 states that read status initiated
2366         * by the MCE shall change the MSE read status. */
2367        if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) {
2368            Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle);
2369            ContentValues contentValues = new ContentValues();
2370            contentValues.put(Sms.READ, statusValue);
2371            contentValues.put(Sms.SEEN, statusValue);
2372            String values = contentValues.toString();
2373            if (D) Log.d(TAG, " -> SMS Uri: " + uri.toString() + " values " + values);
2374            synchronized(getMsgListSms()) {
2375                Msg msg = getMsgListSms().get(handle);
2376                if(msg != null) { // This will always be the case
2377                    msg.flagRead = statusValue;
2378                }
2379            }
2380            count = mResolver.update(uri, contentValues, null, null);
2381            if (D) Log.d(TAG, " -> "+count +" rows updated!");
2382
2383        } else if (type == TYPE.MMS) {
2384            Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
2385            if (D) Log.d(TAG, " -> MMS Uri: " + uri.toString());
2386            ContentValues contentValues = new ContentValues();
2387            contentValues.put(Mms.READ, statusValue);
2388            synchronized(getMsgListMms()) {
2389                Msg msg = getMsgListMms().get(handle);
2390                if(msg != null) { // This will always be the case
2391                    msg.flagRead = statusValue;
2392                }
2393            }
2394            count = mResolver.update(uri, contentValues, null, null);
2395            if (D) Log.d(TAG, " -> "+count +" rows updated!");
2396        } else if (type == TYPE.EMAIL ||
2397                type == TYPE.IM) {
2398            Uri uri = mMessageUri;
2399            ContentValues contentValues = new ContentValues();
2400            contentValues.put(BluetoothMapContract.MessageColumns.FLAG_READ, statusValue);
2401            contentValues.put(BluetoothMapContract.MessageColumns._ID, handle);
2402            synchronized(getMsgListMsg()) {
2403                Msg msg = getMsgListMsg().get(handle);
2404                if(msg != null) { // This will always be the case
2405                    msg.flagRead = statusValue;
2406                }
2407            }
2408            count = mProviderClient.update(uri, contentValues, null, null);
2409        }
2410
2411        return (count > 0);
2412    }
2413
2414    private class PushMsgInfo {
2415        long id;
2416        int transparent;
2417        int retry;
2418        String phone;
2419        Uri uri;
2420        long timestamp;
2421        int parts;
2422        int partsSent;
2423        int partsDelivered;
2424        boolean resend;
2425        boolean sendInProgress;
2426        boolean failedSent; // Set to true if a single part sent fail is received.
2427        int statusDelivered; // Set to != 0 if a single part deliver fail is received.
2428
2429        public PushMsgInfo(long id, int transparent,
2430                int retry, String phone, Uri uri) {
2431            this.id = id;
2432            this.transparent = transparent;
2433            this.retry = retry;
2434            this.phone = phone;
2435            this.uri = uri;
2436            this.resend = false;
2437            this.sendInProgress = false;
2438            this.failedSent = false;
2439            this.statusDelivered = 0; /* Assume success */
2440            this.timestamp = 0;
2441        };
2442    }
2443
2444    private Map<Long, PushMsgInfo> mPushMsgList =
2445            Collections.synchronizedMap(new HashMap<Long, PushMsgInfo>());
2446
2447    public long pushMessage(BluetoothMapbMessage msg, BluetoothMapFolderElement folderElement,
2448            BluetoothMapAppParams ap, String emailBaseUri)
2449                    throws IllegalArgumentException, RemoteException, IOException {
2450        if (D) Log.d(TAG, "pushMessage");
2451        ArrayList<BluetoothMapbMessage.vCard> recipientList = msg.getRecipients();
2452        int transparent = (ap.getTransparent() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) ?
2453                0 : ap.getTransparent();
2454        int retry = ap.getRetry();
2455        int charset = ap.getCharset();
2456        long handle = -1;
2457        long folderId = -1;
2458
2459        if (recipientList == null) {
2460            if (folderElement.getName().equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT)) {
2461                BluetoothMapbMessage.vCard empty =
2462                    new BluetoothMapbMessage.vCard("", "", null, null, 0);
2463                recipientList = new ArrayList<BluetoothMapbMessage.vCard>();
2464                recipientList.add(empty);
2465                Log.w(TAG, "Added empty recipient to draft message");
2466            } else {
2467                Log.e(TAG, "Trying to send a message with no recipients");
2468                return -1;
2469            }
2470        }
2471
2472        if ( msg.getType().equals(TYPE.EMAIL) ) {
2473            /* Write the message to the database */
2474            String msgBody = ((BluetoothMapbMessageEmail) msg).getEmailBody();
2475            if (V) {
2476                int length = msgBody.length();
2477                Log.v(TAG, "pushMessage: message string length = " + length);
2478                String[] messages = msgBody.split("\r\n");
2479                Log.v(TAG, "pushMessage: messages count=" + messages.length);
2480                for(int i = 0; i < messages.length; i++) {
2481                    Log.v(TAG, "part " + i + ":" + messages[i]);
2482                }
2483            }
2484            FileOutputStream os = null;
2485            ParcelFileDescriptor fdOut = null;
2486            Uri uriInsert = Uri.parse(emailBaseUri + BluetoothMapContract.TABLE_MESSAGE);
2487            if (D) Log.d(TAG, "pushMessage - uriInsert= " + uriInsert.toString() +
2488                    ", intoFolder id=" + folderElement.getFolderId());
2489
2490            synchronized(getMsgListMsg()) {
2491                // Now insert the empty message into folder
2492                ContentValues values = new ContentValues();
2493                folderId = folderElement.getFolderId();
2494                values.put(BluetoothMapContract.MessageColumns.FOLDER_ID, folderId);
2495                Uri uriNew = mProviderClient.insert(uriInsert, values);
2496                if (D) Log.d(TAG, "pushMessage - uriNew= " + uriNew.toString());
2497                handle =  Long.parseLong(uriNew.getLastPathSegment());
2498
2499                try {
2500                    fdOut = mProviderClient.openFile(uriNew, "w");
2501                    os = new FileOutputStream(fdOut.getFileDescriptor());
2502                    // Write Email to DB
2503                    os.write(msgBody.getBytes(), 0, msgBody.getBytes().length);
2504                } catch (FileNotFoundException e) {
2505                    Log.w(TAG, e);
2506                    throw(new IOException("Unable to open file stream"));
2507                } catch (NullPointerException e) {
2508                    Log.w(TAG, e);
2509                    throw(new IllegalArgumentException("Unable to parse message."));
2510                } finally {
2511                    try {
2512                        if(os != null)
2513                            os.close();
2514                    } catch (IOException e) {Log.w(TAG, e);}
2515                    try {
2516                        if(fdOut != null)
2517                            fdOut.close();
2518                    } catch (IOException e) {Log.w(TAG, e);}
2519                }
2520
2521                /* Extract the data for the inserted message, and store in local mirror, to
2522                 * avoid sending a NewMessage Event. */
2523                /*TODO: We need to add the new 1.1 parameter as well:-) e.g. read*/
2524                Msg newMsg = new Msg(handle, folderId, 1); // TODO: Create define for read-state
2525                newMsg.transparent = transparent == 1;
2526                if ( folderId == folderElement.getFolderByName(
2527                        BluetoothMapContract.FOLDER_NAME_OUTBOX).getFolderId() ) {
2528                    newMsg.localInitiatedSend = true;
2529                }
2530                getMsgListMsg().put(handle, newMsg);
2531            }
2532        } else { // type SMS_* of MMS
2533            for (BluetoothMapbMessage.vCard recipient : recipientList) {
2534                // Only send the message to the top level recipient
2535                if(recipient.getEnvLevel() == 0)
2536                {
2537                    /* Only send to first address */
2538                    String phone = recipient.getFirstPhoneNumber();
2539                    String email = recipient.getFirstEmail();
2540                    String folder = folderElement.getName();
2541                    boolean read = false;
2542                    boolean deliveryReport = true;
2543                    String msgBody = null;
2544
2545                    /* If MMS contains text only and the size is less than ten SMS's
2546                     * then convert the MMS to type SMS and then proceed
2547                     */
2548                    if (msg.getType().equals(TYPE.MMS) &&
2549                            (((BluetoothMapbMessageMime) msg).getTextOnly())) {
2550                        msgBody = ((BluetoothMapbMessageMime) msg).getMessageAsText();
2551                        SmsManager smsMng = SmsManager.getDefault();
2552                        ArrayList<String> parts = smsMng.divideMessage(msgBody);
2553                        int smsParts = parts.size();
2554                        if (smsParts  <= CONVERT_MMS_TO_SMS_PART_COUNT ) {
2555                            if (D) Log.d(TAG, "pushMessage - converting MMS to SMS, sms parts="
2556                                    + smsParts );
2557                            msg.setType(mSmsType);
2558                        } else {
2559                            if (D) Log.d(TAG, "pushMessage - MMS text only but to big to " +
2560                                    "convert to SMS");
2561                            msgBody = null;
2562                        }
2563
2564                    }
2565
2566                    if (msg.getType().equals(TYPE.MMS)) {
2567                        /* Send message if folder is outbox else just store in draft*/
2568                        handle = sendMmsMessage(folder, phone, (BluetoothMapbMessageMime)msg,
2569                                transparent, retry);
2570                    } else if (msg.getType().equals(TYPE.SMS_GSM) ||
2571                            msg.getType().equals(TYPE.SMS_CDMA) ) {
2572                        /* Add the message to the database */
2573                        if(msgBody == null)
2574                            msgBody = ((BluetoothMapbMessageSms) msg).getSmsBody();
2575
2576                        if (TextUtils.isEmpty(msgBody)) {
2577                            Log.d(TAG, "PushMsg: Empty msgBody ");
2578                            /* not allowed to push empty message */
2579                            throw new IllegalArgumentException("push EMPTY message: Invalid Body");
2580                        }
2581                        /* We need to lock the SMS list while updating the database,
2582                         * to avoid sending events on MCE initiated operation. */
2583                        Uri contentUri = Uri.parse(Sms.CONTENT_URI+ "/" + folder);
2584                        Uri uri;
2585                        synchronized(getMsgListSms()) {
2586                            uri = Sms.addMessageToUri(mResolver, contentUri, phone, msgBody,
2587                                    "", System.currentTimeMillis(), read, deliveryReport);
2588
2589                            if(V) Log.v(TAG, "Sms.addMessageToUri() returned: " + uri);
2590                            if (uri == null) {
2591                                if (D) Log.d(TAG, "pushMessage - failure on add to uri "
2592                                        + contentUri);
2593                                return -1;
2594                            }
2595                            Cursor c = mResolver.query(uri, SMS_PROJECTION_SHORT, null, null, null);
2596
2597                            /* Extract the data for the inserted message, and store in local mirror,
2598                             * to avoid sending a NewMessage Event. */
2599                            try {
2600                                if (c != null && c.moveToFirst()) {
2601                                    long id = c.getLong(c.getColumnIndex(Sms._ID));
2602                                    int type = c.getInt(c.getColumnIndex(Sms.TYPE));
2603                                    int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
2604                                    int readFlag = c.getInt(c.getColumnIndex(Sms.READ));
2605                                    if(V) Log.v(TAG, "add message with id=" + id +
2606                                            " type=" + type + " threadId=" + threadId +
2607                                            " readFlag=" + readFlag + "to mMsgListSms");
2608                                    Msg newMsg = new Msg(id, type, threadId, readFlag);
2609                                    getMsgListSms().put(id, newMsg);
2610                                    c.close();
2611                                } else {
2612                                    Log.w(TAG,"Message: " + uri + " no longer exist!");
2613                                    /* This can only happen, if the message is deleted
2614                                     * just as it is added */
2615                                    return -1;
2616                                }
2617                            } finally {
2618                                if (c != null) c.close();
2619                            }
2620
2621                            handle = Long.parseLong(uri.getLastPathSegment());
2622
2623                            /* Send message if folder is outbox */
2624                            if (folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)) {
2625                                PushMsgInfo msgInfo = new PushMsgInfo(handle, transparent,
2626                                        retry, phone, uri);
2627                                mPushMsgList.put(handle, msgInfo);
2628                                sendMessage(msgInfo, msgBody);
2629                                if(V) Log.v(TAG, "sendMessage returned...");
2630                            } /* else just added to draft */
2631
2632                            /* sendMessage causes the message to be deleted and reinserted,
2633                             * hence we need to lock the list while this is happening. */
2634                        }
2635                    } else {
2636                        if (D) Log.d(TAG, "pushMessage - failure on type " );
2637                        return -1;
2638                    }
2639                }
2640            }
2641        }
2642
2643        /* If multiple recipients return handle of last */
2644        return handle;
2645    }
2646
2647    public long sendMmsMessage(String folder, String to_address, BluetoothMapbMessageMime msg,
2648            int transparent, int retry) {
2649        /*
2650         *strategy:
2651         *1) parse message into parts
2652         *if folder is outbox/drafts:
2653         *2) push message to draft
2654         *if folder is outbox:
2655         *3) move message to outbox (to trigger the mms app to add msg to pending_messages list)
2656         *4) send intent to mms app in order to wake it up.
2657         *else if folder !outbox:
2658         *1) push message to folder
2659         * */
2660        if (folder != null && (folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)
2661                ||  folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT))) {
2662            long handle = pushMmsToFolder(Mms.MESSAGE_BOX_DRAFTS, to_address, msg);
2663            /* if invalid handle (-1) then just return the handle
2664             * - else continue sending (if folder is outbox) */
2665            if (BluetoothMapAppParams.INVALID_VALUE_PARAMETER != handle &&
2666                    folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)) {
2667                Uri btMmsUri = MmsFileProvider.CONTENT_URI.buildUpon()
2668                        .appendPath(Long.toString(handle)).build();
2669                Intent sentIntent = new Intent(ACTION_MESSAGE_SENT);
2670                // TODO: update the mmsMsgList <- done in pushMmsToFolder() but check
2671                sentIntent.setType("message/" + Long.toString(handle));
2672                sentIntent.putExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.MMS.ordinal());
2673                sentIntent.putExtra(EXTRA_MESSAGE_SENT_HANDLE, handle); // needed for notification
2674                sentIntent.putExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, transparent);
2675                sentIntent.putExtra(EXTRA_MESSAGE_SENT_RETRY, retry);
2676                //sentIntent.setDataAndNormalize(btMmsUri);
2677                PendingIntent pendingSendIntent = PendingIntent.getBroadcast(mContext, 0,
2678                        sentIntent, 0);
2679                SmsManager.getDefault().sendMultimediaMessage(mContext,
2680                        btMmsUri, null/*locationUrl*/, null/*configOverrides*/,
2681                        pendingSendIntent);
2682            }
2683            return handle;
2684        } else {
2685            /* not allowed to push mms to anything but outbox/draft */
2686            throw  new IllegalArgumentException("Cannot push message to other " +
2687                    "folders than outbox/draft");
2688        }
2689    }
2690
2691    private void moveDraftToOutbox(long handle) {
2692        moveMmsToFolder(handle, mResolver, Mms.MESSAGE_BOX_OUTBOX);
2693    }
2694
2695    /**
2696     * Move a MMS to another folder.
2697     * @param handle the CP handle of the message to move
2698     * @param resolver the ContentResolver to use
2699     * @param folder the destination folder - use Mms.MESSAGE_BOX_xxx
2700     */
2701    private static void moveMmsToFolder(long handle, ContentResolver resolver, int folder) {
2702        /*Move message by changing the msg_box value in the content provider database */
2703        if (handle != -1) {
2704            String whereClause = " _id= " + handle;
2705            Uri uri = Mms.CONTENT_URI;
2706            Cursor queryResult = resolver.query(uri, null, whereClause, null, null);
2707            try {
2708                if (queryResult != null) {
2709                    if (queryResult.getCount() > 0) {
2710                        queryResult.moveToFirst();
2711                        ContentValues data = new ContentValues();
2712                        /* set folder to be outbox */
2713                        data.put(Mms.MESSAGE_BOX, folder);
2714                        resolver.update(uri, data, whereClause, null);
2715                        if (D) Log.d(TAG, "moved MMS message to " + getMmsFolderName(folder));
2716                    }
2717                } else {
2718                    Log.w(TAG, "Could not move MMS message to " + getMmsFolderName(folder));
2719                }
2720            } finally {
2721                if (queryResult != null) queryResult.close();
2722            }
2723        }
2724    }
2725    private long pushMmsToFolder(int folder, String to_address, BluetoothMapbMessageMime msg) {
2726        /**
2727         * strategy:
2728         * 1) parse msg into parts + header
2729         * 2) create thread id (abuse the ease of adding an SMS to get id for thread)
2730         * 3) push parts into content://mms/parts/ table
2731         * 3)
2732         */
2733
2734        ContentValues values = new ContentValues();
2735        values.put(Mms.MESSAGE_BOX, folder);
2736        values.put(Mms.READ, 0);
2737        values.put(Mms.SEEN, 0);
2738        if(msg.getSubject() != null) {
2739            values.put(Mms.SUBJECT, msg.getSubject());
2740        } else {
2741            values.put(Mms.SUBJECT, "");
2742        }
2743
2744        if(msg.getSubject() != null && msg.getSubject().length() > 0) {
2745            values.put(Mms.SUBJECT_CHARSET, 106);
2746        }
2747        values.put(Mms.CONTENT_TYPE, "application/vnd.wap.multipart.related");
2748        values.put(Mms.EXPIRY, 604800);
2749        values.put(Mms.MESSAGE_CLASS, PduHeaders.MESSAGE_CLASS_PERSONAL_STR);
2750        values.put(Mms.MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_SEND_REQ);
2751        values.put(Mms.MMS_VERSION, PduHeaders.CURRENT_MMS_VERSION);
2752        values.put(Mms.PRIORITY, PduHeaders.PRIORITY_NORMAL);
2753        values.put(Mms.READ_REPORT, PduHeaders.VALUE_NO);
2754        values.put(Mms.TRANSACTION_ID, "T"+ Long.toHexString(System.currentTimeMillis()));
2755        values.put(Mms.DELIVERY_REPORT, PduHeaders.VALUE_NO);
2756        values.put(Mms.LOCKED, 0);
2757        if(msg.getTextOnly())
2758            values.put(Mms.TEXT_ONLY, true);
2759        values.put(Mms.MESSAGE_SIZE, msg.getSize());
2760
2761        // Get thread id
2762        Set<String> recipients = new HashSet<String>();
2763        recipients.addAll(Arrays.asList(to_address));
2764        values.put(Mms.THREAD_ID, Telephony.Threads.getOrCreateThreadId(mContext, recipients));
2765        Uri uri = Mms.CONTENT_URI;
2766
2767        synchronized (getMsgListMms()) {
2768
2769            uri = mResolver.insert(uri, values);
2770
2771            if (uri == null) {
2772                // unable to insert MMS
2773                Log.e(TAG, "Unabled to insert MMS " + values + "Uri: " + uri);
2774                return -1;
2775            }
2776            /* As we already have all the values we need, we could skip the query, but
2777               doing the query ensures we get any changes made by the content provider
2778               at insert. */
2779            Cursor c = mResolver.query(uri, MMS_PROJECTION_SHORT, null, null, null);
2780            try {
2781                if (c != null && c.moveToFirst()) {
2782                    long id = c.getLong(c.getColumnIndex(Mms._ID));
2783                    int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
2784                    int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
2785                    int readStatus = c.getInt(c.getColumnIndex(Mms.READ));
2786
2787                    /* We must filter out any actions made by the MCE. Add the new message to
2788                     * the list of known messages. */
2789
2790                    Msg newMsg = new Msg(id, type, threadId, readStatus);
2791                    newMsg.localInitiatedSend = true;
2792                    getMsgListMms().put(id, newMsg);
2793                    c.close();
2794                }
2795            } finally {
2796                if (c != null) c.close();
2797            }
2798        } // Done adding changes, unlock access to mMsgListMms to allow sending MMS events again
2799
2800        long handle = Long.parseLong(uri.getLastPathSegment());
2801        if (V) Log.v(TAG, " NEW URI " + uri.toString());
2802
2803        try {
2804            if(msg.getMimeParts() == null) {
2805                /* Perhaps this message have been deleted, and no longer have any content,
2806                 * but only headers */
2807                Log.w(TAG, "No MMS parts present...");
2808            } else {
2809                if(V) Log.v(TAG, "Adding " + msg.getMimeParts().size()
2810                        + " parts to the data base.");
2811                for(MimePart part : msg.getMimeParts()) {
2812                    int count = 0;
2813                    count++;
2814                    values.clear();
2815                    if(part.mContentType != null &&
2816                            part.mContentType.toUpperCase().contains("TEXT")) {
2817                        values.put(Mms.Part.CONTENT_TYPE, "text/plain");
2818                        values.put(Mms.Part.CHARSET, 106);
2819                        if(part.mPartName != null) {
2820                            values.put(Mms.Part.FILENAME, part.mPartName);
2821                            values.put(Mms.Part.NAME, part.mPartName);
2822                        } else {
2823                            values.put(Mms.Part.FILENAME, "text_" + count +".txt");
2824                            values.put(Mms.Part.NAME, "text_" + count +".txt");
2825                        }
2826                        // Ensure we have "ci" set
2827                        if(part.mContentId != null) {
2828                            values.put(Mms.Part.CONTENT_ID, part.mContentId);
2829                        } else {
2830                            if(part.mPartName != null) {
2831                                values.put(Mms.Part.CONTENT_ID, "<" + part.mPartName + ">");
2832                            } else {
2833                                values.put(Mms.Part.CONTENT_ID, "<text_" + count + ">");
2834                            }
2835                        }
2836                        // Ensure we have "cl" set
2837                        if(part.mContentLocation != null) {
2838                            values.put(Mms.Part.CONTENT_LOCATION, part.mContentLocation);
2839                        } else {
2840                            if(part.mPartName != null) {
2841                                values.put(Mms.Part.CONTENT_LOCATION, part.mPartName + ".txt");
2842                            } else {
2843                                values.put(Mms.Part.CONTENT_LOCATION, "text_" + count + ".txt");
2844                            }
2845                        }
2846
2847                        if(part.mContentDisposition != null) {
2848                            values.put(Mms.Part.CONTENT_DISPOSITION, part.mContentDisposition);
2849                        }
2850                        values.put(Mms.Part.TEXT, part.getDataAsString());
2851                        uri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/part");
2852                        uri = mResolver.insert(uri, values);
2853                        if(V) Log.v(TAG, "Added TEXT part");
2854
2855                    } else if (part.mContentType != null &&
2856                            part.mContentType.toUpperCase().contains("SMIL")){
2857                        values.put(Mms.Part.SEQ, -1);
2858                        values.put(Mms.Part.CONTENT_TYPE, "application/smil");
2859                        if(part.mContentId != null) {
2860                            values.put(Mms.Part.CONTENT_ID, part.mContentId);
2861                        } else {
2862                            values.put(Mms.Part.CONTENT_ID, "<smil_" + count + ">");
2863                        }
2864                        if(part.mContentLocation != null) {
2865                            values.put(Mms.Part.CONTENT_LOCATION, part.mContentLocation);
2866                        } else {
2867                            values.put(Mms.Part.CONTENT_LOCATION, "smil_" + count + ".xml");
2868                        }
2869
2870                        if(part.mContentDisposition != null)
2871                            values.put(Mms.Part.CONTENT_DISPOSITION, part.mContentDisposition);
2872                        values.put(Mms.Part.FILENAME, "smil.xml");
2873                        values.put(Mms.Part.NAME, "smil.xml");
2874                        values.put(Mms.Part.TEXT, new String(part.mData, "UTF-8"));
2875
2876                        uri = Uri.parse(Mms.CONTENT_URI+ "/" + handle + "/part");
2877                        uri = mResolver.insert(uri, values);
2878                        if (V) Log.v(TAG, "Added SMIL part");
2879
2880                    }else /*VIDEO/AUDIO/IMAGE*/ {
2881                        writeMmsDataPart(handle, part, count);
2882                        if (V) Log.v(TAG, "Added OTHER part");
2883                    }
2884                    if (uri != null){
2885                        if (V) Log.v(TAG, "Added part with content-type: " + part.mContentType
2886                                + " to Uri: " + uri.toString());
2887                    }
2888                }
2889            }
2890        } catch (UnsupportedEncodingException e) {
2891            Log.w(TAG, e);
2892        } catch (IOException e) {
2893            Log.w(TAG, e);
2894        }
2895
2896        values.clear();
2897        values.put(Mms.Addr.CONTACT_ID, "null");
2898        values.put(Mms.Addr.ADDRESS, "insert-address-token");
2899        values.put(Mms.Addr.TYPE, BluetoothMapContent.MMS_FROM);
2900        values.put(Mms.Addr.CHARSET, 106);
2901
2902        uri = Uri.parse(Mms.CONTENT_URI + "/"  + handle + "/addr");
2903        uri = mResolver.insert(uri, values);
2904        if (uri != null && V){
2905            Log.v(TAG, " NEW URI " + uri.toString());
2906        }
2907
2908        values.clear();
2909        values.put(Mms.Addr.CONTACT_ID, "null");
2910        values.put(Mms.Addr.ADDRESS, to_address);
2911        values.put(Mms.Addr.TYPE, BluetoothMapContent.MMS_TO);
2912        values.put(Mms.Addr.CHARSET, 106);
2913
2914        uri = Uri.parse(Mms.CONTENT_URI + "/"  + handle + "/addr");
2915        uri = mResolver.insert(uri, values);
2916        if (uri != null && V){
2917            Log.v(TAG, " NEW URI " + uri.toString());
2918        }
2919        return handle;
2920    }
2921
2922
2923    private void writeMmsDataPart(long handle, MimePart part, int count) throws IOException{
2924        ContentValues values = new ContentValues();
2925        values.put(Mms.Part.MSG_ID, handle);
2926        if(part.mContentType != null) {
2927            values.put(Mms.Part.CONTENT_TYPE, part.mContentType);
2928        } else {
2929            Log.w(TAG, "MMS has no CONTENT_TYPE for part " + count);
2930        }
2931        if(part.mContentId != null) {
2932            values.put(Mms.Part.CONTENT_ID, part.mContentId);
2933        } else {
2934            if(part.mPartName != null) {
2935                values.put(Mms.Part.CONTENT_ID, "<" + part.mPartName + ">");
2936            } else {
2937                values.put(Mms.Part.CONTENT_ID, "<part_" + count + ">");
2938            }
2939        }
2940
2941        if(part.mContentLocation != null) {
2942            values.put(Mms.Part.CONTENT_LOCATION, part.mContentLocation);
2943        } else {
2944            if(part.mPartName != null) {
2945                values.put(Mms.Part.CONTENT_LOCATION, part.mPartName + ".dat");
2946            } else {
2947                values.put(Mms.Part.CONTENT_LOCATION, "part_" + count + ".dat");
2948            }
2949        }
2950        if(part.mContentDisposition != null)
2951            values.put(Mms.Part.CONTENT_DISPOSITION, part.mContentDisposition);
2952        if(part.mPartName != null) {
2953            values.put(Mms.Part.FILENAME, part.mPartName);
2954            values.put(Mms.Part.NAME, part.mPartName);
2955        } else {
2956            /* We must set at least one part identifier */
2957            values.put(Mms.Part.FILENAME, "part_" + count + ".dat");
2958            values.put(Mms.Part.NAME, "part_" + count + ".dat");
2959        }
2960        Uri partUri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/part");
2961        Uri res = mResolver.insert(partUri, values);
2962
2963        // Add data to part
2964        OutputStream os = mResolver.openOutputStream(res);
2965        os.write(part.mData);
2966        os.close();
2967    }
2968
2969
2970    public void sendMessage(PushMsgInfo msgInfo, String msgBody) {
2971
2972        SmsManager smsMng = SmsManager.getDefault();
2973        ArrayList<String> parts = smsMng.divideMessage(msgBody);
2974        msgInfo.parts = parts.size();
2975        // We add a time stamp to differentiate delivery reports from each other for resent messages
2976        msgInfo.timestamp = Calendar.getInstance().getTime().getTime();
2977        msgInfo.partsDelivered = 0;
2978        msgInfo.partsSent = 0;
2979
2980        ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>(msgInfo.parts);
2981        ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(msgInfo.parts);
2982
2983        /*       We handle the SENT intent in the MAP service, as this object
2984         *       is destroyed at disconnect, hence if a disconnect occur while sending
2985         *       a message, there is no intent handler to move the message from outbox
2986         *       to the correct folder.
2987         *       The correct solution would be to create a service that will start based on
2988         *       the intent, if BT is turned off. */
2989
2990        if (parts != null && parts.size() > 0) {
2991            for (int i = 0; i < msgInfo.parts; i++) {
2992                Intent intentDelivery, intentSent;
2993
2994                intentDelivery = new Intent(ACTION_MESSAGE_DELIVERY, null);
2995                /* Add msgId and part number to ensure the intents are different, and we
2996                 * thereby get an intent for each msg part.
2997                 * setType is needed to create different intents for each message id/ time stamp,
2998                 * as the extras are not used when comparing. */
2999                intentDelivery.setType("message/" + Long.toString(msgInfo.id) +
3000                        msgInfo.timestamp + i);
3001                intentDelivery.putExtra(EXTRA_MESSAGE_SENT_HANDLE, msgInfo.id);
3002                intentDelivery.putExtra(EXTRA_MESSAGE_SENT_TIMESTAMP, msgInfo.timestamp);
3003                PendingIntent pendingIntentDelivery = PendingIntent.getBroadcast(mContext, 0,
3004                        intentDelivery, PendingIntent.FLAG_UPDATE_CURRENT);
3005
3006                intentSent = new Intent(ACTION_MESSAGE_SENT, null);
3007                /* Add msgId and part number to ensure the intents are different, and we
3008                 * thereby get an intent for each msg part.
3009                 * setType is needed to create different intents for each message id/ time stamp,
3010                 * as the extras are not used when comparing. */
3011                intentSent.setType("message/" + Long.toString(msgInfo.id) + msgInfo.timestamp + i);
3012                intentSent.putExtra(EXTRA_MESSAGE_SENT_HANDLE, msgInfo.id);
3013                intentSent.putExtra(EXTRA_MESSAGE_SENT_URI, msgInfo.uri.toString());
3014                intentSent.putExtra(EXTRA_MESSAGE_SENT_RETRY, msgInfo.retry);
3015                intentSent.putExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, msgInfo.transparent);
3016
3017                PendingIntent pendingIntentSent = PendingIntent.getBroadcast(mContext, 0,
3018                        intentSent, PendingIntent.FLAG_UPDATE_CURRENT);
3019
3020                // We use the same pending intent for all parts, but do not set the one shot flag.
3021                deliveryIntents.add(pendingIntentDelivery);
3022                sentIntents.add(pendingIntentSent);
3023            }
3024
3025            Log.d(TAG, "sendMessage to " + msgInfo.phone);
3026
3027            smsMng.sendMultipartTextMessage(msgInfo.phone, null, parts, sentIntents,
3028                    deliveryIntents);
3029        }
3030    }
3031
3032    private class SmsBroadcastReceiver extends BroadcastReceiver {
3033        private final String[] ID_PROJECTION = new String[] { Sms._ID };
3034        private final Uri UPDATE_STATUS_URI = Uri.withAppendedPath(Sms.CONTENT_URI, "/status");
3035
3036        public void register() {
3037            Handler handler = new Handler(Looper.getMainLooper());
3038
3039            IntentFilter intentFilter = new IntentFilter();
3040            intentFilter.addAction(ACTION_MESSAGE_DELIVERY);
3041            try{
3042                intentFilter.addDataType("message/*");
3043            } catch (MalformedMimeTypeException e) {
3044                Log.e(TAG, "Wrong mime type!!!", e);
3045            }
3046
3047            mContext.registerReceiver(this, intentFilter, null, handler);
3048        }
3049
3050        public void unregister() {
3051            try {
3052                mContext.unregisterReceiver(this);
3053            } catch (IllegalArgumentException e) {
3054                /* do nothing */
3055            }
3056        }
3057
3058        @Override
3059        public void onReceive(Context context, Intent intent) {
3060            String action = intent.getAction();
3061            long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1);
3062            PushMsgInfo msgInfo = mPushMsgList.get(handle);
3063
3064            Log.d(TAG, "onReceive: action"  + action);
3065
3066            if (msgInfo == null) {
3067                Log.d(TAG, "onReceive: no msgInfo found for handle " + handle);
3068                return;
3069            }
3070
3071            if (action.equals(ACTION_MESSAGE_SENT)) {
3072                int result = intent.getIntExtra(EXTRA_MESSAGE_SENT_RESULT,
3073                        Activity.RESULT_CANCELED);
3074                msgInfo.partsSent++;
3075                if(result != Activity.RESULT_OK) {
3076                    /* If just one of the parts in the message fails, we need to send the
3077                     * entire message again
3078                     */
3079                    msgInfo.failedSent = true;
3080                }
3081                if(D) Log.d(TAG, "onReceive: msgInfo.partsSent = " + msgInfo.partsSent
3082                        + ", msgInfo.parts = " + msgInfo.parts + " result = " + result);
3083
3084                if (msgInfo.partsSent == msgInfo.parts) {
3085                    actionMessageSent(context, intent, msgInfo);
3086                }
3087            } else if (action.equals(ACTION_MESSAGE_DELIVERY)) {
3088                long timestamp = intent.getLongExtra(EXTRA_MESSAGE_SENT_TIMESTAMP, 0);
3089                int status = -1;
3090                if(msgInfo.timestamp == timestamp) {
3091                    msgInfo.partsDelivered++;
3092                }
3093            } else {
3094                Log.d(TAG, "onReceive: Unknown action " + action);
3095            }
3096        }
3097
3098        private void actionMessageSent(Context context, Intent intent, PushMsgInfo msgInfo) {
3099            /* As the MESSAGE_SENT intent is forwarded from the MAP service, we use the intent
3100             * to carry the result, as getResult() will not return the correct value.
3101             */
3102            boolean delete = false;
3103
3104            if(D) Log.d(TAG,"actionMessageSent(): msgInfo.failedSent = " + msgInfo.failedSent);
3105
3106            msgInfo.sendInProgress = false;
3107
3108            if (!msgInfo.failedSent) {
3109                if(D) Log.d(TAG, "actionMessageSent: result OK");
3110                if (msgInfo.transparent == 0) {
3111                    if (!Sms.moveMessageToFolder(context, msgInfo.uri,
3112                            Sms.MESSAGE_TYPE_SENT, 0)) {
3113                        Log.w(TAG, "Failed to move " + msgInfo.uri + " to SENT");
3114                    }
3115                } else {
3116                    delete = true;
3117                }
3118
3119                Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msgInfo.id,
3120                        getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType);
3121                sendEvent(evt);
3122
3123            } else {
3124                if (msgInfo.retry == 1) {
3125                    /* Notify failure, but keep message in outbox for resending */
3126                    msgInfo.resend = true;
3127                    msgInfo.partsSent = 0; // Reset counter for the retry
3128                    msgInfo.failedSent = false;
3129                    Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, msgInfo.id,
3130                            getSmsFolderName(Sms.MESSAGE_TYPE_OUTBOX), null, mSmsType);
3131                    sendEvent(evt);
3132                } else {
3133                    if (msgInfo.transparent == 0) {
3134                        if (!Sms.moveMessageToFolder(context, msgInfo.uri,
3135                                Sms.MESSAGE_TYPE_FAILED, 0)) {
3136                            Log.w(TAG, "Failed to move " + msgInfo.uri + " to FAILED");
3137                        }
3138                    } else {
3139                        delete = true;
3140                    }
3141
3142                    Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, msgInfo.id,
3143                            getSmsFolderName(Sms.MESSAGE_TYPE_FAILED), null, mSmsType);
3144                    sendEvent(evt);
3145                }
3146            }
3147
3148            if (delete) {
3149                /* Delete from Observer message list to avoid delete notifications */
3150                synchronized(getMsgListSms()) {
3151                    getMsgListSms().remove(msgInfo.id);
3152                }
3153
3154                /* Delete from DB */
3155                mResolver.delete(msgInfo.uri, null, null);
3156            }
3157        }
3158
3159        private void actionMessageDelivery(Context context, Intent intent, PushMsgInfo msgInfo) {
3160            Uri messageUri = intent.getData();
3161            msgInfo.sendInProgress = false;
3162
3163            Cursor cursor = mResolver.query(msgInfo.uri, ID_PROJECTION, null, null, null);
3164
3165            try {
3166                if (cursor.moveToFirst()) {
3167                    int messageId = cursor.getInt(0);
3168
3169                    Uri updateUri = ContentUris.withAppendedId(UPDATE_STATUS_URI, messageId);
3170
3171                    if(D) Log.d(TAG, "actionMessageDelivery: uri=" + messageUri + ", status="
3172                            + msgInfo.statusDelivered);
3173
3174                    ContentValues contentValues = new ContentValues(2);
3175
3176                    contentValues.put(Sms.STATUS, msgInfo.statusDelivered);
3177                    contentValues.put(Inbox.DATE_SENT, System.currentTimeMillis());
3178                    mResolver.update(updateUri, contentValues, null, null);
3179                } else {
3180                    Log.d(TAG, "Can't find message for status update: " + messageUri);
3181                }
3182            } finally {
3183                if (cursor != null) cursor.close();
3184            }
3185
3186            if (msgInfo.statusDelivered == 0) {
3187                Event evt = new Event(EVENT_TYPE_DELEVERY_SUCCESS, msgInfo.id,
3188                        getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType);
3189                sendEvent(evt);
3190            } else {
3191                Event evt = new Event(EVENT_TYPE_DELIVERY_FAILURE, msgInfo.id,
3192                        getSmsFolderName(Sms.MESSAGE_TYPE_SENT), null, mSmsType);
3193                sendEvent(evt);
3194            }
3195
3196            mPushMsgList.remove(msgInfo.id);
3197        }
3198    }
3199
3200    private class CeBroadcastReceiver extends BroadcastReceiver {
3201        public void register() {
3202            UserManager manager = UserManager.get(mContext);
3203            if (manager == null || manager.isUserUnlocked()) {
3204                mStorageUnlocked = true;
3205                return;
3206            }
3207
3208            Handler handler = new Handler(Looper.getMainLooper());
3209            IntentFilter intentFilter = new IntentFilter();
3210            intentFilter.addAction(Intent.ACTION_BOOT_COMPLETED);
3211            mContext.registerReceiver(this, intentFilter, null, handler);
3212        }
3213
3214        public void unregister() {
3215            try {
3216                mContext.unregisterReceiver(this);
3217            } catch (IllegalArgumentException e) {
3218                /* do nothing */
3219            }
3220        }
3221
3222        @Override
3223        public void onReceive(Context context, Intent intent) {
3224            String action = intent.getAction();
3225            Log.d(TAG, "onReceive: action"  + action);
3226
3227            if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
3228                try {
3229                    initMsgList();
3230                } catch (RemoteException e) {
3231                    Log.e(TAG, "Error initializing SMS/MMS message lists.");
3232                }
3233
3234                for (String folder : FOLDER_SMS_MAP.values()) {
3235                    Event evt = new Event(EVENT_TYPE_NEW, -1, folder, mSmsType);
3236                    sendEvent(evt);
3237                }
3238                mStorageUnlocked = true;
3239                /* After unlock this BroadcastReceiver is never needed */
3240                unregister();
3241            } else {
3242                Log.d(TAG, "onReceive: Unknown action " + action);
3243            }
3244        }
3245    }
3246
3247    /**
3248     * Handle MMS sent intents in disconnected(MNS) state, where we do not need to send any
3249     * notifications.
3250     * @param context The context to use for provider operations
3251     * @param intent The intent received
3252     * @param result The result
3253     */
3254    public static void actionMmsSent(Context context, Intent intent, int result,
3255            Map<Long, Msg> mmsMsgList) {
3256        /*
3257         * if transparent:
3258         *   delete message and send notification(regardless of result)
3259         * else
3260         *   Result == Success:
3261         *     move to sent folder (will trigger notification)
3262         *   Result == Fail:
3263         *     move to outbox (send delivery fail notification)
3264         */
3265        if(D) Log.d(TAG,"actionMmsSent()");
3266        int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
3267        long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1);
3268        if(handle < 0) {
3269            Log.w(TAG, "Intent received for an invalid handle");
3270            return;
3271        }
3272        ContentResolver resolver = context.getContentResolver();
3273        if(transparent == 1) {
3274            /* The specification is a bit unclear about the transparent flag. If it is set
3275             * no copy of the message shall be kept in the send folder after the message
3276             * was send, but in the case of a send error, it is unclear what to do.
3277             * As it will not be transparent if we keep the message in any folder,
3278             * we delete the message regardless of the result.
3279             * If we however do have a MNS connection we need to send a notification. */
3280            Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
3281            /* Delete from observer message list to avoid delete notifications */
3282            if(mmsMsgList != null) {
3283                synchronized(mmsMsgList) {
3284                    mmsMsgList.remove(handle);
3285                }
3286            }
3287            /* Delete message */
3288            if(D) Log.d(TAG,"Transparent in use - delete");
3289            resolver.delete(uri, null, null);
3290        } else if (result == Activity.RESULT_OK) {
3291            /* This will trigger a notification */
3292            moveMmsToFolder(handle, resolver, Mms.MESSAGE_BOX_SENT);
3293        } else {
3294            if(mmsMsgList != null) {
3295                synchronized(mmsMsgList) {
3296                    Msg msg = mmsMsgList.get(handle);
3297                    if(msg != null) {
3298                    msg.type=Mms.MESSAGE_BOX_OUTBOX;
3299                    }
3300                }
3301            }
3302            /* Hand further retries over to the MMS application */
3303            moveMmsToFolder(handle, resolver, Mms.MESSAGE_BOX_OUTBOX);
3304        }
3305    }
3306
3307    public static void actionMessageSentDisconnected(Context context, Intent intent, int result) {
3308        TYPE type = TYPE.fromOrdinal(
3309        intent.getIntExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal()));
3310        if(type == TYPE.MMS) {
3311            actionMmsSent(context, intent, result, null);
3312        } else {
3313            actionSmsSentDisconnected(context, intent, result);
3314        }
3315    }
3316
3317    public static void actionSmsSentDisconnected(Context context, Intent intent, int result) {
3318        /* Check permission for message deletion. */
3319        if ((Binder.getCallingPid() != Process.myPid()) ||
3320            (context.checkCallingOrSelfPermission("android.Manifest.permission.WRITE_SMS")
3321                    != PackageManager.PERMISSION_GRANTED)) {
3322            Log.w(TAG, "actionSmsSentDisconnected: Not allowed to delete SMS/MMS messages");
3323            return;
3324        }
3325
3326        boolean delete = false;
3327        //int retry = intent.getIntExtra(EXTRA_MESSAGE_SENT_RETRY, 0);
3328        int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
3329        String uriString = intent.getStringExtra(EXTRA_MESSAGE_SENT_URI);
3330        if(uriString == null) {
3331            // Nothing we can do about it, just bail out
3332            return;
3333        }
3334        Uri uri = Uri.parse(uriString);
3335
3336        if (result == Activity.RESULT_OK) {
3337            Log.d(TAG, "actionMessageSentDisconnected: result OK");
3338            if (transparent == 0) {
3339                if (!Sms.moveMessageToFolder(context, uri,
3340                        Sms.MESSAGE_TYPE_SENT, 0)) {
3341                    Log.d(TAG, "Failed to move " + uri + " to SENT");
3342                }
3343            } else {
3344                delete = true;
3345            }
3346        } else {
3347            /*if (retry == 1) {
3348                 The retry feature only works while connected, else we fail the send,
3349             * and move the message to failed, to let the user/app resend manually later.
3350            } else */{
3351                if (transparent == 0) {
3352                    if (!Sms.moveMessageToFolder(context, uri,
3353                            Sms.MESSAGE_TYPE_FAILED, 0)) {
3354                        Log.d(TAG, "Failed to move " + uri + " to FAILED");
3355                    }
3356                } else {
3357                    delete = true;
3358                }
3359            }
3360        }
3361
3362        if (delete) {
3363            /* Delete from DB */
3364            ContentResolver resolver = context.getContentResolver();
3365            if (resolver != null) {
3366                resolver.delete(uri, null, null);
3367            } else {
3368                Log.w(TAG, "Unable to get resolver");
3369            }
3370        }
3371    }
3372
3373    private void registerPhoneServiceStateListener() {
3374        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(
3375                Context.TELEPHONY_SERVICE);
3376        tm.listen(mPhoneListener, PhoneStateListener.LISTEN_SERVICE_STATE);
3377    }
3378
3379    private void unRegisterPhoneServiceStateListener() {
3380        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(
3381                Context.TELEPHONY_SERVICE);
3382        tm.listen(mPhoneListener, PhoneStateListener.LISTEN_NONE);
3383    }
3384
3385    private void resendPendingMessages() {
3386        /* Send pending messages in outbox */
3387        String where = "type = " + Sms.MESSAGE_TYPE_OUTBOX;
3388        UserManager manager = UserManager.get(mContext);
3389        if (manager == null || !manager.isUserUnlocked()) return;
3390        Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
3391                null);
3392        try {
3393            if (c != null && c.moveToFirst()) {
3394                do {
3395                    long id = c.getLong(c.getColumnIndex(Sms._ID));
3396                    String msgBody = c.getString(c.getColumnIndex(Sms.BODY));
3397                    PushMsgInfo msgInfo = mPushMsgList.get(id);
3398                    if (msgInfo == null || !msgInfo.resend ||
3399                        msgInfo.sendInProgress) {
3400                        continue;
3401                    }
3402                    msgInfo.sendInProgress = true;
3403                    sendMessage(msgInfo, msgBody);
3404                } while (c.moveToNext());
3405            }
3406        } finally {
3407            if (c != null) c.close();
3408        }
3409
3410
3411    }
3412
3413    private void failPendingMessages() {
3414        /* Move pending messages from outbox to failed */
3415        String where = "type = " + Sms.MESSAGE_TYPE_OUTBOX;
3416        Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
3417                null);
3418        try {
3419            if (c != null && c.moveToFirst()) {
3420                do {
3421                    long id = c.getLong(c.getColumnIndex(Sms._ID));
3422                    String msgBody = c.getString(c.getColumnIndex(Sms.BODY));
3423                    PushMsgInfo msgInfo = mPushMsgList.get(id);
3424                    if (msgInfo == null || !msgInfo.resend) {
3425                        continue;
3426                    }
3427                    Sms.moveMessageToFolder(mContext, msgInfo.uri,
3428                            Sms.MESSAGE_TYPE_FAILED, 0);
3429                } while (c.moveToNext());
3430            }
3431        } finally {
3432            if (c != null) c.close();
3433        }
3434
3435    }
3436
3437    private void removeDeletedMessages() {
3438        /* Remove messages from virtual "deleted" folder (thread_id -1) */
3439        mResolver.delete(Sms.CONTENT_URI,
3440                "thread_id = " + DELETED_THREAD_ID, null);
3441    }
3442
3443    private PhoneStateListener mPhoneListener = new PhoneStateListener() {
3444        @Override
3445        public void onServiceStateChanged(ServiceState serviceState) {
3446            Log.d(TAG, "Phone service state change: " + serviceState.getState());
3447            if (serviceState.getState() == ServiceState.STATE_IN_SERVICE) {
3448                resendPendingMessages();
3449            }
3450        }
3451    };
3452
3453    public void init() {
3454        if (mSmsBroadcastReceiver != null) {
3455            mSmsBroadcastReceiver.register();
3456        }
3457
3458        if (mCeBroadcastReceiver != null) {
3459            mCeBroadcastReceiver.register();
3460        }
3461
3462        registerPhoneServiceStateListener();
3463        mInitialized = true;
3464    }
3465
3466    public void deinit() {
3467        mInitialized = false;
3468        unregisterObserver();
3469        if (mSmsBroadcastReceiver != null) {
3470            mSmsBroadcastReceiver.unregister();
3471        }
3472        unRegisterPhoneServiceStateListener();
3473        failPendingMessages();
3474        removeDeletedMessages();
3475    }
3476
3477    public boolean handleSmsSendIntent(Context context, Intent intent){
3478        TYPE type = TYPE.fromOrdinal(
3479            intent.getIntExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal()));
3480        if(type == TYPE.MMS) {
3481            return handleMmsSendIntent(context, intent);
3482        } else {
3483            if(mInitialized) {
3484                mSmsBroadcastReceiver.onReceive(context, intent);
3485                return true;
3486            }
3487        }
3488        return false;
3489    }
3490
3491    public boolean handleMmsSendIntent(Context context, Intent intent){
3492        if(D) Log.w(TAG, "handleMmsSendIntent()");
3493        if(!mMnsClient.isConnected()) {
3494            // No need to handle notifications, just use default handling
3495            if(D) Log.w(TAG, "MNS not connected - use static handling");
3496            return false;
3497        }
3498        long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1);
3499        int result = intent.getIntExtra(EXTRA_MESSAGE_SENT_RESULT, Activity.RESULT_CANCELED);
3500        actionMmsSent(context, intent, result, getMsgListMms());
3501        if(handle < 0) {
3502            Log.w(TAG, "Intent received for an invalid handle");
3503            return true;
3504        }
3505        if(result != Activity.RESULT_OK) {
3506            if(mObserverRegistered) {
3507                Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, handle,
3508                        getMmsFolderName(Mms.MESSAGE_BOX_OUTBOX), null, TYPE.MMS);
3509                sendEvent(evt);
3510            }
3511        } else {
3512            int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
3513            if(transparent != 0) {
3514                if(mObserverRegistered) {
3515                    Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, handle,
3516                            getMmsFolderName(Mms.MESSAGE_BOX_OUTBOX), null, TYPE.MMS);
3517                    sendEvent(evt);
3518                }
3519            }
3520        }
3521        return true;
3522    }
3523
3524}
3525