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