Home | History | Annotate | Download | only in map
      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  */
     15 package com.android.bluetooth.map;
     16 
     17 import android.annotation.TargetApi;
     18 import android.app.Activity;
     19 import android.app.PendingIntent;
     20 import android.content.BroadcastReceiver;
     21 import android.content.ContentProviderClient;
     22 import android.content.ContentResolver;
     23 import android.content.ContentUris;
     24 import android.content.ContentValues;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.IntentFilter;
     28 import android.content.IntentFilter.MalformedMimeTypeException;
     29 import android.content.pm.PackageManager;
     30 import android.database.ContentObserver;
     31 import android.database.Cursor;
     32 import android.net.Uri;
     33 import android.os.Binder;
     34 import android.os.Handler;
     35 import android.os.Looper;
     36 import android.os.Message;
     37 import android.os.ParcelFileDescriptor;
     38 import android.os.Process;
     39 import android.os.RemoteException;
     40 import android.provider.Telephony;
     41 import android.provider.Telephony.Mms;
     42 import android.provider.Telephony.MmsSms;
     43 import android.provider.Telephony.Sms;
     44 import android.provider.Telephony.Sms.Inbox;
     45 import android.telephony.PhoneStateListener;
     46 import android.telephony.ServiceState;
     47 import android.telephony.SmsManager;
     48 import android.telephony.SmsMessage;
     49 import android.telephony.TelephonyManager;
     50 import android.text.format.DateUtils;
     51 import android.util.Log;
     52 import android.util.Xml;
     53 import android.text.TextUtils;
     54 
     55 import org.xmlpull.v1.XmlSerializer;
     56 
     57 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
     58 import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart;
     59 import com.android.bluetooth.mapapi.BluetoothMapContract;
     60 import com.android.bluetooth.mapapi.BluetoothMapContract.MessageColumns;
     61 import com.google.android.mms.pdu.PduHeaders;
     62 
     63 import java.io.FileNotFoundException;
     64 import java.io.FileOutputStream;
     65 import java.io.IOException;
     66 import java.io.OutputStream;
     67 import java.io.StringWriter;
     68 import java.io.UnsupportedEncodingException;
     69 import java.util.ArrayList;
     70 import java.util.Arrays;
     71 import java.util.Calendar;
     72 import java.util.Collections;
     73 import java.util.HashMap;
     74 import java.util.HashSet;
     75 import java.util.Map;
     76 import java.util.Set;
     77 
     78 import javax.obex.ResponseCodes;
     79 
     80 @TargetApi(19)
     81 public class BluetoothMapContentObserver {
     82     private static final String TAG = "BluetoothMapContentObserver";
     83 
     84     private static final boolean D = BluetoothMapService.DEBUG;
     85     private static final boolean V = BluetoothMapService.VERBOSE;
     86 
     87     private static final String EVENT_TYPE_NEW              = "NewMessage";
     88     private static final String EVENT_TYPE_DELETE           = "MessageDeleted";
     89     private static final String EVENT_TYPE_REMOVED          = "MessageRemoved";
     90     private static final String EVENT_TYPE_SHIFT            = "MessageShift";
     91     private static final String EVENT_TYPE_DELEVERY_SUCCESS = "DeliverySuccess";
     92     private static final String EVENT_TYPE_SENDING_SUCCESS  = "SendingSuccess";
     93     private static final String EVENT_TYPE_SENDING_FAILURE  = "SendingFailure";
     94     private static final String EVENT_TYPE_DELIVERY_FAILURE = "DeliveryFailure";
     95     private static final String EVENT_TYPE_READ_STATUS      = "ReadStatusChanged";
     96     private static final String EVENT_TYPE_CONVERSATION     = "ConversationChanged";
     97     private static final String EVENT_TYPE_PRESENCE         = "ParticipantPresenceChanged";
     98     private static final String EVENT_TYPE_CHAT_STATE       = "ParticipantChatStateChanged";
     99 
    100     private static final long EVENT_FILTER_NEW_MESSAGE                  = 1L;
    101     private static final long EVENT_FILTER_MESSAGE_DELETED              = 1L<<1;
    102     private static final long EVENT_FILTER_MESSAGE_SHIFT                = 1L<<2;
    103     private static final long EVENT_FILTER_SENDING_SUCCESS              = 1L<<3;
    104     private static final long EVENT_FILTER_SENDING_FAILED               = 1L<<4;
    105     private static final long EVENT_FILTER_DELIVERY_SUCCESS             = 1L<<5;
    106     private static final long EVENT_FILTER_DELIVERY_FAILED              = 1L<<6;
    107     private static final long EVENT_FILTER_MEMORY_FULL                  = 1L<<7; // Unused
    108     private static final long EVENT_FILTER_MEMORY_AVAILABLE             = 1L<<8; // Unused
    109     private static final long EVENT_FILTER_READ_STATUS_CHANGED          = 1L<<9;
    110     private static final long EVENT_FILTER_CONVERSATION_CHANGED         = 1L<<10;
    111     private static final long EVENT_FILTER_PARTICIPANT_PRESENCE_CHANGED = 1L<<11;
    112     private static final long EVENT_FILTER_PARTICIPANT_CHATSTATE_CHANGED= 1L<<12;
    113     private static final long EVENT_FILTER_MESSAGE_REMOVED              = 1L<<13;
    114 
    115     // TODO: If we are requesting a large message from the network, on a slow connection
    116     //       20 seconds might not be enough... But then again 20 seconds is long for other
    117     //       cases.
    118     private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS;
    119 
    120     private Context mContext;
    121     private ContentResolver mResolver;
    122     private ContentProviderClient mProviderClient = null;
    123     private BluetoothMnsObexClient mMnsClient;
    124     private BluetoothMapMasInstance mMasInstance = null;
    125     private int mMasId;
    126     private boolean mEnableSmsMms = false;
    127     private boolean mObserverRegistered = false;
    128     private BluetoothMapAccountItem mAccount;
    129     private String mAuthority = null;
    130 
    131     // Default supported feature bit mask is 0x1f
    132     private int mMapSupportedFeatures = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
    133     // Default event report version is 1.0
    134     private int mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V10;
    135 
    136     private BluetoothMapFolderElement mFolders =
    137             new BluetoothMapFolderElement("DUMMY", null); // Will be set by the MAS when generated.
    138     private Uri mMessageUri = null;
    139     private Uri mContactUri = null;
    140 
    141     private boolean mTransmitEvents = true;
    142 
    143     /* To make the filter update atomic, we declare it volatile.
    144      * To avoid a penalty when using it, copy the value to a local
    145      * non-volatile variable when used more than once.
    146      * Actually we only ever use the lower 4 bytes of this variable,
    147      * hence we could manage without the volatile keyword, but as
    148      * we tend to copy ways of doing things, we better do it right:-) */
    149     private volatile long mEventFilter = 0xFFFFFFFFL;
    150 
    151     public static final int DELETED_THREAD_ID = -1;
    152 
    153     // X-Mms-Message-Type field types. These are from PduHeaders.java
    154     public static final int MESSAGE_TYPE_RETRIEVE_CONF = 0x84;
    155 
    156     // Text only MMS converted to SMS if sms parts less than or equal to defined count
    157     private static final int CONVERT_MMS_TO_SMS_PART_COUNT = 10;
    158 
    159     private TYPE mSmsType;
    160 
    161     private static final String ACTION_MESSAGE_DELIVERY =
    162             "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY";
    163     /*package*/ static final String ACTION_MESSAGE_SENT =
    164         "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT";
    165 
    166     public static final String EXTRA_MESSAGE_SENT_HANDLE = "HANDLE";
    167     public static final String EXTRA_MESSAGE_SENT_RESULT = "result";
    168     public static final String EXTRA_MESSAGE_SENT_MSG_TYPE = "type";
    169     public static final String EXTRA_MESSAGE_SENT_URI = "uri";
    170     public static final String EXTRA_MESSAGE_SENT_RETRY = "retry";
    171     public static final String EXTRA_MESSAGE_SENT_TRANSPARENT = "transparent";
    172     public static final String EXTRA_MESSAGE_SENT_TIMESTAMP = "timestamp";
    173 
    174     private SmsBroadcastReceiver mSmsBroadcastReceiver = new SmsBroadcastReceiver();
    175 
    176     private boolean mInitialized = false;
    177 
    178 
    179     static final String[] SMS_PROJECTION = new String[] {
    180         Sms._ID,
    181         Sms.THREAD_ID,
    182         Sms.ADDRESS,
    183         Sms.BODY,
    184         Sms.DATE,
    185         Sms.READ,
    186         Sms.TYPE,
    187         Sms.STATUS,
    188         Sms.LOCKED,
    189         Sms.ERROR_CODE
    190     };
    191 
    192     static final String[] SMS_PROJECTION_SHORT = new String[] {
    193         Sms._ID,
    194         Sms.THREAD_ID,
    195         Sms.TYPE,
    196         Sms.READ
    197     };
    198 
    199     static final String[] SMS_PROJECTION_SHORT_EXT = new String[] {
    200         Sms._ID,
    201         Sms.THREAD_ID,
    202         Sms.ADDRESS,
    203         Sms.BODY,
    204         Sms.DATE,
    205         Sms.READ,
    206         Sms.TYPE,
    207     };
    208 
    209     static final String[] MMS_PROJECTION_SHORT = new String[] {
    210         Mms._ID,
    211         Mms.THREAD_ID,
    212         Mms.MESSAGE_TYPE,
    213         Mms.MESSAGE_BOX,
    214         Mms.READ
    215     };
    216 
    217     static final String[] MMS_PROJECTION_SHORT_EXT = new String[] {
    218         Mms._ID,
    219         Mms.THREAD_ID,
    220         Mms.MESSAGE_TYPE,
    221         Mms.MESSAGE_BOX,
    222         Mms.READ,
    223         Mms.DATE,
    224         Mms.SUBJECT,
    225         Mms.PRIORITY
    226     };
    227 
    228     static final String[] MSG_PROJECTION_SHORT = new String[] {
    229         BluetoothMapContract.MessageColumns._ID,
    230         BluetoothMapContract.MessageColumns.FOLDER_ID,
    231         BluetoothMapContract.MessageColumns.FLAG_READ
    232     };
    233 
    234     static final String[] MSG_PROJECTION_SHORT_EXT = new String[] {
    235         BluetoothMapContract.MessageColumns._ID,
    236         BluetoothMapContract.MessageColumns.FOLDER_ID,
    237         BluetoothMapContract.MessageColumns.FLAG_READ,
    238         BluetoothMapContract.MessageColumns.DATE,
    239         BluetoothMapContract.MessageColumns.SUBJECT,
    240         BluetoothMapContract.MessageColumns.FROM_LIST,
    241         BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY
    242     };
    243 
    244     static final String[] MSG_PROJECTION_SHORT_EXT2 = new String[] {
    245         BluetoothMapContract.MessageColumns._ID,
    246         BluetoothMapContract.MessageColumns.FOLDER_ID,
    247         BluetoothMapContract.MessageColumns.FLAG_READ,
    248         BluetoothMapContract.MessageColumns.DATE,
    249         BluetoothMapContract.MessageColumns.SUBJECT,
    250         BluetoothMapContract.MessageColumns.FROM_LIST,
    251         BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY,
    252         BluetoothMapContract.MessageColumns.THREAD_ID,
    253         BluetoothMapContract.MessageColumns.THREAD_NAME
    254     };
    255 
    256     public BluetoothMapContentObserver(final Context context,
    257             BluetoothMnsObexClient mnsClient,
    258             BluetoothMapMasInstance masInstance,
    259             BluetoothMapAccountItem account,
    260             boolean enableSmsMms) throws RemoteException {
    261         mContext = context;
    262         mResolver = mContext.getContentResolver();
    263         mAccount = account;
    264         mMasInstance = masInstance;
    265         mMasId = mMasInstance.getMasId();
    266 
    267         mMapSupportedFeatures = mMasInstance.getRemoteFeatureMask();
    268         if (D) Log.d(TAG, "BluetoothMapContentObserver: Supported features " +
    269                 Integer.toHexString(mMapSupportedFeatures) ) ;
    270 
    271         if((BluetoothMapUtils.MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT
    272                 & mMapSupportedFeatures) != 0){
    273             mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
    274         }
    275         // Make sure support for all formats result in latest version returned
    276         if((BluetoothMapUtils.MAP_FEATURE_EVENT_REPORT_V12_BIT
    277                 & mMapSupportedFeatures) != 0){
    278             mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
    279         }
    280 
    281         if(account != null) {
    282             mAuthority = Uri.parse(account.mBase_uri).getAuthority();
    283             mMessageUri = Uri.parse(account.mBase_uri + "/" + BluetoothMapContract.TABLE_MESSAGE);
    284             if (mAccount.getType() == TYPE.IM) {
    285                 mContactUri = Uri.parse(account.mBase_uri + "/"
    286                         + BluetoothMapContract.TABLE_CONVOCONTACT);
    287             }
    288             // TODO: We need to release this again!
    289             mProviderClient = mResolver.acquireUnstableContentProviderClient(mAuthority);
    290             if (mProviderClient == null) {
    291                 throw new RemoteException("Failed to acquire provider for " + mAuthority);
    292             }
    293             mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
    294             mContactList = mMasInstance.getContactList();
    295             if(mContactList == null) {
    296                 setContactList(new HashMap<String, BluetoothMapConvoContactElement>(), false);
    297                 initContactsList();
    298             }
    299         }
    300         mEnableSmsMms = enableSmsMms;
    301         mSmsType = getSmsType();
    302         mMnsClient = mnsClient;
    303         /* Get the cached list - if any, else create */
    304         mMsgListSms = mMasInstance.getMsgListSms();
    305         boolean doInit = false;
    306         if(mEnableSmsMms) {
    307             if(mMsgListSms == null) {
    308                 setMsgListSms(new HashMap<Long, Msg>(), false);
    309                 doInit = true;
    310             }
    311             mMsgListMms = mMasInstance.getMsgListMms();
    312             if(mMsgListMms == null) {
    313                 setMsgListMms(new HashMap<Long, Msg>(), false);
    314                 doInit = true;
    315             }
    316         }
    317         if(mAccount != null) {
    318             mMsgListMsg = mMasInstance.getMsgListMsg();
    319             if(mMsgListMsg == null) {
    320                 setMsgListMsg(new HashMap<Long, Msg>(), false);
    321                 doInit = true;
    322             }
    323         }
    324         if(doInit) {
    325             initMsgList();
    326         }
    327     }
    328 
    329     public int getObserverRemoteFeatureMask() {
    330         if (V) Log.v(TAG, "getObserverRemoteFeatureMask : " + mMapEventReportVersion
    331             + " mMapSupportedFeatures: " + mMapSupportedFeatures);
    332         return mMapSupportedFeatures;
    333     }
    334 
    335     public void setObserverRemoteFeatureMask(int remoteSupportedFeatures) {
    336         mMapSupportedFeatures = remoteSupportedFeatures;
    337         if ((BluetoothMapUtils.MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT
    338                 & mMapSupportedFeatures) != 0) {
    339             mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
    340         }
    341         // Make sure support for all formats result in latest version returned
    342         if ((BluetoothMapUtils.MAP_FEATURE_EVENT_REPORT_V12_BIT
    343                 & mMapSupportedFeatures) != 0) {
    344             mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
    345         }
    346         if (V) Log.d(TAG, "setObserverRemoteFeatureMask : " + mMapEventReportVersion
    347             + " mMapSupportedFeatures : " + mMapSupportedFeatures);
    348     }
    349 
    350     private Map<Long, Msg> getMsgListSms() {
    351         return mMsgListSms;
    352     }
    353 
    354     private void setMsgListSms(Map<Long, Msg> msgListSms, boolean changesDetected) {
    355         mMsgListSms = msgListSms;
    356         if(changesDetected) {
    357             mMasInstance.updateFolderVersionCounter();
    358         }
    359         mMasInstance.setMsgListSms(msgListSms);
    360     }
    361 
    362 
    363     private Map<Long, Msg> getMsgListMms() {
    364         return mMsgListMms;
    365     }
    366 
    367 
    368     private void setMsgListMms(Map<Long, Msg> msgListMms, boolean changesDetected) {
    369         mMsgListMms = msgListMms;
    370         if(changesDetected) {
    371             mMasInstance.updateFolderVersionCounter();
    372         }
    373         mMasInstance.setMsgListMms(msgListMms);
    374     }
    375 
    376 
    377     private Map<Long, Msg> getMsgListMsg() {
    378         return mMsgListMsg;
    379     }
    380 
    381 
    382     private void setMsgListMsg(Map<Long, Msg> msgListMsg, boolean changesDetected) {
    383         mMsgListMsg = msgListMsg;
    384         if(changesDetected) {
    385             mMasInstance.updateFolderVersionCounter();
    386         }
    387         mMasInstance.setMsgListMsg(msgListMsg);
    388     }
    389 
    390     private Map<String, BluetoothMapConvoContactElement> getContactList() {
    391         return mContactList;
    392     }
    393 
    394 
    395     /**
    396      * Currently we only have data for IM / email contacts
    397      * @param contactList
    398      * @param changesDetected that is not chat state changed nor presence state changed.
    399      */
    400     private void setContactList(Map<String, BluetoothMapConvoContactElement> contactList,
    401             boolean changesDetected) {
    402         mContactList = contactList;
    403         if(changesDetected) {
    404             mMasInstance.updateImEmailConvoListVersionCounter();
    405         }
    406         mMasInstance.setContactList(contactList);
    407     }
    408 
    409     private static boolean sendEventNewMessage(long eventFilter) {
    410         return ((eventFilter & EVENT_FILTER_NEW_MESSAGE) > 0);
    411     }
    412 
    413     private static boolean sendEventMessageDeleted(long eventFilter) {
    414         return ((eventFilter & EVENT_FILTER_MESSAGE_DELETED) > 0);
    415     }
    416 
    417     private static boolean sendEventMessageShift(long eventFilter) {
    418         return ((eventFilter & EVENT_FILTER_MESSAGE_SHIFT) > 0);
    419     }
    420 
    421     private static boolean sendEventSendingSuccess(long eventFilter) {
    422         return ((eventFilter & EVENT_FILTER_SENDING_SUCCESS) > 0);
    423     }
    424 
    425     private static boolean sendEventSendingFailed(long eventFilter) {
    426         return ((eventFilter & EVENT_FILTER_SENDING_FAILED) > 0);
    427     }
    428 
    429     private static boolean sendEventDeliverySuccess(long eventFilter) {
    430         return ((eventFilter & EVENT_FILTER_DELIVERY_SUCCESS) > 0);
    431     }
    432 
    433     private static boolean sendEventDeliveryFailed(long eventFilter) {
    434         return ((eventFilter & EVENT_FILTER_DELIVERY_FAILED) > 0);
    435     }
    436 
    437     private static boolean sendEventReadStatusChanged(long eventFilter) {
    438         return ((eventFilter & EVENT_FILTER_READ_STATUS_CHANGED) > 0);
    439     }
    440 
    441     private static boolean sendEventConversationChanged(long eventFilter) {
    442         return ((eventFilter & EVENT_FILTER_CONVERSATION_CHANGED) > 0);
    443     }
    444 
    445     private static boolean sendEventParticipantPresenceChanged(long eventFilter) {
    446         return ((eventFilter & EVENT_FILTER_PARTICIPANT_PRESENCE_CHANGED) > 0);
    447     }
    448 
    449     private static boolean sendEventParticipantChatstateChanged(long eventFilter) {
    450         return ((eventFilter & EVENT_FILTER_PARTICIPANT_CHATSTATE_CHANGED) > 0);
    451     }
    452 
    453     private static boolean sendEventMessageRemoved(long eventFilter) {
    454         return ((eventFilter & EVENT_FILTER_MESSAGE_REMOVED) > 0);
    455     }
    456 
    457     private TYPE getSmsType() {
    458         TYPE smsType = null;
    459         TelephonyManager tm = (TelephonyManager) mContext.getSystemService(
    460                 Context.TELEPHONY_SERVICE);
    461 
    462         if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
    463             smsType = TYPE.SMS_CDMA;
    464         } else {
    465             smsType = TYPE.SMS_GSM;
    466         }
    467 
    468         return smsType;
    469     }
    470 
    471     private final ContentObserver mObserver = new ContentObserver(new Handler()) {
    472         @Override
    473         public void onChange(boolean selfChange) {
    474             onChange(selfChange, null);
    475         }
    476 
    477         @Override
    478         public void onChange(boolean selfChange, Uri uri) {
    479             if(uri == null) {
    480                 Log.w(TAG, "onChange() with URI == null - not handled.");
    481                 return;
    482             }
    483             if (V) Log.d(TAG, "onChange on thread: " + Thread.currentThread().getId()
    484                     + " Uri: " + uri.toString() + " selfchange: " + selfChange);
    485 
    486             if(uri.toString().contains(BluetoothMapContract.TABLE_CONVOCONTACT))
    487                 handleContactListChanges(uri);
    488             else
    489                 handleMsgListChanges(uri);
    490         }
    491     };
    492 
    493     private static final HashMap<Integer, String> FOLDER_SMS_MAP;
    494     static {
    495         FOLDER_SMS_MAP = new HashMap<Integer, String>();
    496         FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_INBOX,  BluetoothMapContract.FOLDER_NAME_INBOX);
    497         FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_SENT,  BluetoothMapContract.FOLDER_NAME_SENT);
    498         FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_DRAFT,  BluetoothMapContract.FOLDER_NAME_DRAFT);
    499         FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_OUTBOX,  BluetoothMapContract.FOLDER_NAME_OUTBOX);
    500         FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_FAILED,  BluetoothMapContract.FOLDER_NAME_OUTBOX);
    501         FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_QUEUED,  BluetoothMapContract.FOLDER_NAME_OUTBOX);
    502     }
    503 
    504     private static String getSmsFolderName(int type) {
    505         String name = FOLDER_SMS_MAP.get(type);
    506         if(name != null) {
    507             return name;
    508         }
    509         Log.e(TAG, "New SMS mailbox types have been introduced, without an update in BT...");
    510         return "Unknown";
    511     }
    512 
    513 
    514     private static final HashMap<Integer, String> FOLDER_MMS_MAP;
    515     static {
    516         FOLDER_MMS_MAP = new HashMap<Integer, String>();
    517         FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_INBOX,  BluetoothMapContract.FOLDER_NAME_INBOX);
    518         FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_SENT,   BluetoothMapContract.FOLDER_NAME_SENT);
    519         FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_DRAFTS, BluetoothMapContract.FOLDER_NAME_DRAFT);
    520         FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_OUTBOX, BluetoothMapContract.FOLDER_NAME_OUTBOX);
    521     }
    522 
    523     private static String getMmsFolderName(int mailbox) {
    524         String name = FOLDER_MMS_MAP.get(mailbox);
    525         if(name != null) {
    526             return name;
    527         }
    528         Log.e(TAG, "New MMS mailboxes have been introduced, without an update in BT...");
    529         return "Unknown";
    530     }
    531 
    532     /**
    533      * Set the folder structure to be used for this instance.
    534      * @param folderStructure
    535      */
    536     public void setFolderStructure(BluetoothMapFolderElement folderStructure) {
    537         this.mFolders = folderStructure;
    538     }
    539 
    540     private class ConvoContactInfo {
    541         public int mConvoColConvoId         = -1;
    542         public int mConvoColLastActivity    = -1;
    543         public int mConvoColName            = -1;
    544         //        public int mConvoColRead            = -1;
    545         //        public int mConvoColVersionCounter  = -1;
    546         public int mContactColUci           = -1;
    547         public int mContactColConvoId       = -1;
    548         public int mContactColName          = -1;
    549         public int mContactColNickname      = -1;
    550         public int mContactColBtUid         = -1;
    551         public int mContactColChatState     = -1;
    552         public int mContactColContactId     = -1;
    553         public int mContactColLastActive    = -1;
    554         public int mContactColPresenceState = -1;
    555         public int mContactColPresenceText  = -1;
    556         public int mContactColPriority      = -1;
    557         public int mContactColLastOnline    = -1;
    558 
    559         public void setConvoColunms(Cursor c) {
    560             //            mConvoColConvoId         = c.getColumnIndex(
    561             //                    BluetoothMapContract.ConversationColumns.THREAD_ID);
    562             //            mConvoColLastActivity    = c.getColumnIndex(
    563             //                    BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY);
    564             //            mConvoColName            = c.getColumnIndex(
    565             //                    BluetoothMapContract.ConversationColumns.THREAD_NAME);
    566             mContactColConvoId       = c.getColumnIndex(
    567                     BluetoothMapContract.ConvoContactColumns.CONVO_ID);
    568             mContactColName          = c.getColumnIndex(
    569                     BluetoothMapContract.ConvoContactColumns.NAME);
    570             mContactColNickname      = c.getColumnIndex(
    571                     BluetoothMapContract.ConvoContactColumns.NICKNAME);
    572             mContactColBtUid         = c.getColumnIndex(
    573                     BluetoothMapContract.ConvoContactColumns.X_BT_UID);
    574             mContactColChatState     = c.getColumnIndex(
    575                     BluetoothMapContract.ConvoContactColumns.CHAT_STATE);
    576             mContactColUci           = c.getColumnIndex(
    577                     BluetoothMapContract.ConvoContactColumns.UCI);
    578             mContactColNickname      = c.getColumnIndex(
    579                     BluetoothMapContract.ConvoContactColumns.NICKNAME);
    580             mContactColLastActive    = c.getColumnIndex(
    581                     BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE);
    582             mContactColName          = c.getColumnIndex(
    583                     BluetoothMapContract.ConvoContactColumns.NAME);
    584             mContactColPresenceState = c.getColumnIndex(
    585                     BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE);
    586             mContactColPresenceText  = c.getColumnIndex(
    587                     BluetoothMapContract.ConvoContactColumns.STATUS_TEXT);
    588             mContactColPriority      = c.getColumnIndex(
    589                     BluetoothMapContract.ConvoContactColumns.PRIORITY);
    590             mContactColLastOnline    = c.getColumnIndex(
    591                     BluetoothMapContract.ConvoContactColumns.LAST_ONLINE);
    592         }
    593     }
    594 
    595     private class Event {
    596         String eventType;
    597         long handle;
    598         String folder = null;
    599         String oldFolder = null;
    600         TYPE msgType;
    601         /* Extended event parameters in MAP Event version 1.1 */
    602         String datetime = null; // OBEX time "YYYYMMDDTHHMMSS"
    603         String uci = null;
    604         String subject = null;
    605         String senderName = null;
    606         String priority = null;
    607         /* Event parameters in MAP Event version 1.2 */
    608         String conversationName = null;
    609         long conversationID = -1;
    610         int presenceState = BluetoothMapContract.PresenceState.UNKNOWN;
    611         String presenceStatus = null;
    612         int chatState = BluetoothMapContract.ChatState.UNKNOWN;
    613 
    614         final static String PATH = "telecom/msg/";
    615 
    616         private void setFolderPath(String name, TYPE type) {
    617             if (name != null) {
    618                 if(type == TYPE.EMAIL || type == TYPE.IM) {
    619                     this.folder = name;
    620                 } else {
    621                     this.folder = PATH + name;
    622                 }
    623             } else {
    624                 this.folder = null;
    625             }
    626         }
    627 
    628         public Event(String eventType, long handle, String folder,
    629                 String oldFolder, TYPE msgType) {
    630             this.eventType = eventType;
    631             this.handle = handle;
    632             setFolderPath(folder, msgType);
    633             if (oldFolder != null) {
    634                 if(msgType == TYPE.EMAIL || msgType == TYPE.IM) {
    635                     this.oldFolder = oldFolder;
    636                 } else {
    637                     this.oldFolder = PATH + oldFolder;
    638                 }
    639             } else {
    640                 this.oldFolder = null;
    641             }
    642             this.msgType = msgType;
    643         }
    644 
    645         public Event(String eventType, long handle, String folder, TYPE msgType) {
    646             this.eventType = eventType;
    647             this.handle = handle;
    648             setFolderPath(folder, msgType);
    649             this.msgType = msgType;
    650         }
    651 
    652         /* extended event type 1.1 */
    653         public Event(String eventType, long handle, String folder, TYPE msgType,
    654                 String datetime, String subject, String senderName, String priority) {
    655             this.eventType = eventType;
    656             this.handle = handle;
    657             setFolderPath(folder, msgType);
    658             this.msgType = msgType;
    659             this.datetime = datetime;
    660             if (subject != null) {
    661                 this.subject = BluetoothMapUtils.stripInvalidChars(subject);
    662             }
    663             if (senderName != null) {
    664                 this.senderName = BluetoothMapUtils.stripInvalidChars(senderName);
    665             }
    666             this.priority = priority;
    667         }
    668 
    669         /* extended event type 1.2 message events */
    670         public Event(String eventType, long handle, String folder, TYPE msgType,
    671                 String datetime, String subject, String senderName, String priority,
    672                 long conversationID, String conversationName) {
    673             this.eventType = eventType;
    674             this.handle = handle;
    675             setFolderPath(folder, msgType);
    676             this.msgType = msgType;
    677             this.datetime = datetime;
    678             if (subject != null) {
    679                 this.subject = BluetoothMapUtils.stripInvalidChars(subject);
    680             }
    681             if (senderName != null) {
    682                 this.senderName = BluetoothMapUtils.stripInvalidChars(senderName);
    683             }
    684             if (conversationID != 0) {
    685                 this.conversationID = conversationID;
    686             }
    687             if (conversationName != null) {
    688                 this.conversationName = BluetoothMapUtils.stripInvalidChars(conversationName);
    689             }
    690             this.priority = priority;
    691         }
    692 
    693         /* extended event type 1.2 for conversation, presence or chat state changed events */
    694         public Event(String eventType, String uci, TYPE msgType, String name, String priority,
    695                 String lastActivity, long conversationID, String conversationName,
    696                 int presenceState, String presenceStatus, int chatState) {
    697             this.eventType = eventType;
    698             this.uci = uci;
    699             this.msgType = msgType;
    700             if (name != null) {
    701                 this.senderName = BluetoothMapUtils.stripInvalidChars(name);
    702             }
    703             this.priority = priority;
    704             this.datetime = lastActivity;
    705             if (conversationID != 0) {
    706                 this.conversationID = conversationID;
    707             }
    708             if (conversationName != null) {
    709                 this.conversationName = BluetoothMapUtils.stripInvalidChars(conversationName);
    710             }
    711             if (presenceState != BluetoothMapContract.PresenceState.UNKNOWN) {
    712                 this.presenceState = presenceState;
    713             }
    714             if (presenceStatus != null) {
    715                 this.presenceStatus = BluetoothMapUtils.stripInvalidChars(presenceStatus);
    716             }
    717             if (chatState != BluetoothMapContract.ChatState.UNKNOWN) {
    718                 this.chatState = chatState;
    719             }
    720         }
    721 
    722         public byte[] encode() throws UnsupportedEncodingException {
    723             StringWriter sw = new StringWriter();
    724             XmlSerializer xmlEvtReport = Xml.newSerializer();
    725 
    726             try {
    727                 xmlEvtReport.setOutput(sw);
    728                 xmlEvtReport.startDocument("UTF-8", true);
    729                 xmlEvtReport.text("\r\n");
    730                 xmlEvtReport.startTag("", "MAP-event-report");
    731                 if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
    732                     xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V10_STR);
    733                 } else if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
    734                     xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V11_STR);
    735                 } else {
    736                     xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V12_STR);
    737                 }
    738                 xmlEvtReport.startTag("", "event");
    739                 xmlEvtReport.attribute("", "type", eventType);
    740                 if (eventType.equals(EVENT_TYPE_CONVERSATION) ||
    741                         eventType.equals(EVENT_TYPE_PRESENCE) ||
    742                         eventType.equals(EVENT_TYPE_CHAT_STATE)) {
    743                     xmlEvtReport.attribute("", "participant_uci", uci);
    744                 } else {
    745                     xmlEvtReport.attribute("", "handle",
    746                             BluetoothMapUtils.getMapHandle(handle, msgType));
    747                 }
    748 
    749                 if (folder != null) {
    750                     xmlEvtReport.attribute("", "folder", folder);
    751                 }
    752                 if (oldFolder != null) {
    753                     xmlEvtReport.attribute("", "old_folder", oldFolder);
    754                 }
    755                 /* Avoid possible NPE for "msgType" "null" value. "msgType"
    756                  * is a implied attribute and will be set "null" for events
    757                  * like "memory full" or "memory available" */
    758                 if (msgType != null) {
    759                     xmlEvtReport.attribute("", "msg_type", msgType.name());
    760                 }
    761                 /* If MAP event report version is above 1.0 send
    762                  * extended event report parameters */
    763                 if (datetime != null) {
    764                     xmlEvtReport.attribute("", "datetime", datetime);
    765                 }
    766                 if (subject != null) {
    767                     xmlEvtReport.attribute("", "subject",
    768                             subject.substring(0,subject.length() < 256 ? subject.length() : 256));
    769                 }
    770                 if (senderName != null) {
    771                     xmlEvtReport.attribute("", "senderName", senderName);
    772                 }
    773                 if (priority != null) {
    774                     xmlEvtReport.attribute("", "priority", priority);
    775                 }
    776 
    777                 //}
    778                 /* Include conversation information from event version 1.2 */
    779                 if (mMapEventReportVersion > BluetoothMapUtils.MAP_EVENT_REPORT_V11 ) {
    780                     if (conversationName != null) {
    781                         xmlEvtReport.attribute("", "conversation_name", conversationName);
    782                     }
    783                     if (conversationID != -1) {
    784                         // Convert provider conversation handle to string incl type
    785                         xmlEvtReport.attribute("", "conversation_id",
    786                                 BluetoothMapUtils.getMapConvoHandle(conversationID, msgType));
    787                     }
    788                     if (eventType.equals(EVENT_TYPE_PRESENCE)) {
    789                         if (presenceState != 0) {
    790                             // Convert provider conversation handle to string incl type
    791                             xmlEvtReport.attribute("", "presence_availability",
    792                                     String.valueOf(presenceState));
    793                         }
    794                         if (presenceStatus != null) {
    795                             // Convert provider conversation handle to string incl type
    796                             xmlEvtReport.attribute("", "presence_status",
    797                                     presenceStatus.substring(
    798                                             0,presenceStatus.length() < 256 ? subject.length() : 256));
    799                         }
    800                     }
    801                     if (eventType.equals(EVENT_TYPE_PRESENCE)) {
    802                         if (chatState != 0) {
    803                             // Convert provider conversation handle to string incl type
    804                             xmlEvtReport.attribute("", "chat_state", String.valueOf(chatState));
    805                         }
    806                     }
    807 
    808                 }
    809                 xmlEvtReport.endTag("", "event");
    810                 xmlEvtReport.endTag("", "MAP-event-report");
    811                 xmlEvtReport.endDocument();
    812             } catch (IllegalArgumentException e) {
    813                 if(D) Log.w(TAG,e);
    814             } catch (IllegalStateException e) {
    815                 if(D) Log.w(TAG,e);
    816             } catch (IOException e) {
    817                 if(D) Log.w(TAG,e);
    818             }
    819 
    820             if (V) Log.d(TAG, sw.toString());
    821 
    822             return sw.toString().getBytes("UTF-8");
    823         }
    824     }
    825 
    826     /*package*/ class Msg {
    827         long id;
    828         int type;               // Used as folder for SMS/MMS
    829         int threadId;           // Used for SMS/MMS at delete
    830         long folderId = -1;     // Email folder ID
    831         long oldFolderId = -1;  // Used for email undelete
    832         boolean localInitiatedSend = false; // Used for MMS to filter out events
    833         boolean transparent = false; // Used for EMAIL to delete message sent with transparency
    834         int flagRead = -1;      // Message status read/unread
    835 
    836         public Msg(long id, int type, int threadId, int readFlag) {
    837             this.id = id;
    838             this.type = type;
    839             this.threadId = threadId;
    840             this.flagRead = readFlag;
    841         }
    842         public Msg(long id, long folderId, int readFlag) {
    843             this.id = id;
    844             this.folderId = folderId;
    845             this.flagRead = readFlag;
    846         }
    847 
    848         /* Eclipse generated hashCode() and equals() to make
    849          * hashMap lookup work independent of whether the obj
    850          * is used for email or SMS/MMS and whether or not the
    851          * oldFolder is set. */
    852         @Override
    853         public int hashCode() {
    854             final int prime = 31;
    855             int result = 1;
    856             result = prime * result + (int) (id ^ (id >>> 32));
    857             return result;
    858         }
    859 
    860         @Override
    861         public boolean equals(Object obj) {
    862             if (this == obj)
    863                 return true;
    864             if (obj == null)
    865                 return false;
    866             if (getClass() != obj.getClass())
    867                 return false;
    868             Msg other = (Msg) obj;
    869             if (id != other.id)
    870                 return false;
    871             return true;
    872         }
    873     }
    874 
    875     private Map<Long, Msg> mMsgListSms = null;
    876 
    877     private Map<Long, Msg> mMsgListMms = null;
    878 
    879     private Map<Long, Msg> mMsgListMsg = null;
    880 
    881     private Map<String, BluetoothMapConvoContactElement> mContactList = null;
    882 
    883     public int setNotificationRegistration(int notificationStatus) throws RemoteException {
    884         // Forward the request to the MNS thread as a message - including the MAS instance ID.
    885         if(D) Log.d(TAG,"setNotificationRegistration() enter");
    886         if (mMnsClient == null) {
    887             return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    888         }
    889         Handler mns = mMnsClient.getMessageHandler();
    890         if (mns != null) {
    891             Message msg = mns.obtainMessage();
    892             if (mMnsClient.isValidMnsRecord()) {
    893                 msg.what = BluetoothMnsObexClient.MSG_MNS_NOTIFICATION_REGISTRATION;
    894             } else {
    895                 //Trigger SDP Search and notificaiton registration , if SDP record not found.
    896                 msg.what = BluetoothMnsObexClient.MSG_MNS_SDP_SEARCH_REGISTRATION;
    897                 if (mMnsClient.mMnsLstRegRqst != null &&
    898                         (mMnsClient.mMnsLstRegRqst.isSearchInProgress())) {
    899                     /*  1. Disallow next Notification ON Request :
    900                      *     - Respond "Service Unavailable" as SDP Search and last notification
    901                      *       registration ON request is already InProgress.
    902                      *     - Next notification ON Request will be allowed ONLY after search
    903                      *       and connect for last saved request [Replied with OK ] is processed.
    904                      */
    905                     if (notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) {
    906                         return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    907                     } else {
    908                         /*  2. Allow next Notification OFF Request:
    909                          *    - Keep the SDP search still in progress.
    910                          *    - Disconnect and Deregister the contentObserver.
    911                          */
    912                         msg.what = BluetoothMnsObexClient.MSG_MNS_NOTIFICATION_REGISTRATION;
    913                     }
    914                 }
    915             }
    916             msg.arg1 = mMasId;
    917             msg.arg2 = notificationStatus;
    918             mns.sendMessageDelayed(msg, 10); // Send message without forcing a context switch
    919             /* Some devices - e.g. PTS needs to get the unregister confirm before we actually
    920              * disconnect the MNS. */
    921             if(D) Log.d(TAG,"setNotificationRegistration() send : " + msg.what + " to MNS ");
    922             return ResponseCodes.OBEX_HTTP_OK;
    923         } else {
    924             // This should not happen except at shutdown.
    925             if(D) Log.d(TAG,"setNotificationRegistration() Unable to send registration request");
    926             return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    927         }
    928     }
    929 
    930     boolean eventMaskContainsContacts(long mask) {
    931         return sendEventParticipantPresenceChanged(mask);
    932     }
    933 
    934     boolean eventMaskContainsCovo(long mask) {
    935         return (sendEventConversationChanged(mask)
    936                 || sendEventParticipantChatstateChanged(mask));
    937     }
    938 
    939     /* Overwrite the existing notification filter. Will register/deregister observers for
    940      * the Contacts and Conversation table as needed. We keep the message observer
    941      * at all times. */
    942     /*package*/ synchronized void setNotificationFilter(long newFilter) {
    943         long oldFilter = mEventFilter;
    944         mEventFilter = newFilter;
    945         /* Contacts */
    946         if(!eventMaskContainsContacts(oldFilter) &&
    947                 eventMaskContainsContacts(newFilter)) {
    948             // TODO:
    949             // Enable the observer
    950             // Reset the contacts list
    951         }
    952         /* Conversations */
    953         if(!eventMaskContainsCovo(oldFilter) &&
    954                 eventMaskContainsCovo(newFilter)) {
    955             // TODO:
    956             // Enable the observer
    957             // Reset the conversations list
    958         }
    959     }
    960 
    961     public void registerObserver() throws RemoteException{
    962         if (V) Log.d(TAG, "registerObserver");
    963 
    964         if (mObserverRegistered)
    965             return;
    966 
    967         if(mAccount != null) {
    968 
    969             mProviderClient = mResolver.acquireUnstableContentProviderClient(mAuthority);
    970             if (mProviderClient == null) {
    971                 throw new RemoteException("Failed to acquire provider for " + mAuthority);
    972             }
    973             mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
    974 
    975             // If there is a change in the database before we init the lists we will be sending
    976             // loads of events - hence init before register.
    977             if(mAccount.getType() == TYPE.IM) {
    978                 // Further add contact list tracking
    979                 initContactsList();
    980             }
    981         }
    982         // If there is a change in the database before we init the lists we will be sending
    983         // loads of events - hence init before register.
    984         initMsgList();
    985 
    986         /* Use MmsSms Uri since the Sms Uri is not notified on deletes */
    987         if(mEnableSmsMms){
    988             //this is sms/mms
    989             mResolver.registerContentObserver(MmsSms.CONTENT_URI, false, mObserver);
    990             mObserverRegistered = true;
    991         }
    992 
    993         if(mAccount != null) {
    994             /* For URI's without account ID */
    995             Uri uri = Uri.parse(mAccount.mBase_uri_no_account + "/"
    996                     + BluetoothMapContract.TABLE_MESSAGE);
    997             if(D) Log.d(TAG, "Registering observer for: " + uri);
    998             mResolver.registerContentObserver(uri, true, mObserver);
    999 
   1000             /* For URI's with account ID - is handled the same way as without ID, but is
   1001              * only triggered for MAS instances with matching account ID. */
   1002             uri = Uri.parse(mAccount.mBase_uri + "/" + BluetoothMapContract.TABLE_MESSAGE);
   1003             if(D) Log.d(TAG, "Registering observer for: " + uri);
   1004             mResolver.registerContentObserver(uri, true, mObserver);
   1005 
   1006             if(mAccount.getType() == TYPE.IM) {
   1007 
   1008                 uri = Uri.parse(mAccount.mBase_uri_no_account + "/"
   1009                         + BluetoothMapContract.TABLE_CONVOCONTACT);
   1010                 if(D) Log.d(TAG, "Registering observer for: " + uri);
   1011                 mResolver.registerContentObserver(uri, true, mObserver);
   1012 
   1013                 /* For URI's with account ID - is handled the same way as without ID, but is
   1014                  * only triggered for MAS instances with matching account ID. */
   1015                 uri = Uri.parse(mAccount.mBase_uri + "/" + BluetoothMapContract.TABLE_CONVOCONTACT);
   1016                 if(D) Log.d(TAG, "Registering observer for: " + uri);
   1017                 mResolver.registerContentObserver(uri, true, mObserver);
   1018             }
   1019 
   1020             mObserverRegistered = true;
   1021         }
   1022     }
   1023 
   1024     public void unregisterObserver() {
   1025         if (V) Log.d(TAG, "unregisterObserver");
   1026         mResolver.unregisterContentObserver(mObserver);
   1027         mObserverRegistered = false;
   1028         if(mProviderClient != null){
   1029             mProviderClient.release();
   1030             mProviderClient = null;
   1031         }
   1032     }
   1033 
   1034     /**
   1035      * Per design it is only possible to call the refreshXxxx functions sequentially, hence it
   1036      * is safe to modify mTransmitEvents without synchronization.
   1037      */
   1038     /* package */ void refreshFolderVersionCounter() {
   1039         if (mObserverRegistered) {
   1040             // As we have observers, we already keep the counter up-to-date.
   1041             return;
   1042         }
   1043         /* We need to perform the same functionality, as when we receive a notification change,
   1044            hence we:
   1045             - disable the event transmission
   1046             - triggers the code for updates
   1047             - enable the event transmission */
   1048         mTransmitEvents = false;
   1049         try {
   1050             if(mEnableSmsMms) {
   1051                 handleMsgListChangesSms();
   1052                 handleMsgListChangesMms();
   1053             }
   1054             if(mAccount != null) {
   1055                 try {
   1056                     handleMsgListChangesMsg(mMessageUri);
   1057                 } catch (RemoteException e) {
   1058                     Log.e(TAG, "Unable to update FolderVersionCounter. - Not fatal, but can cause" +
   1059                             " undesirable user experience!", e);
   1060                 }
   1061             }
   1062         } finally {
   1063             // Ensure we always enable events again
   1064             mTransmitEvents = true;
   1065         }
   1066     }
   1067 
   1068     /* package */ void refreshConvoListVersionCounter() {
   1069         if (mObserverRegistered) {
   1070             // As we have observers, we already keep the counter up-to-date.
   1071             return;
   1072         }
   1073         /* We need to perform the same functionality, as when we receive a notification change,
   1074         hence we:
   1075          - disable event transmission
   1076          - triggers the code for updates
   1077          - enable event transmission */
   1078         mTransmitEvents = false;
   1079         try {
   1080             if((mAccount != null) && (mContactUri != null)) {
   1081                 handleContactListChanges(mContactUri);
   1082             }
   1083         } finally {
   1084             // Ensure we always enable events again
   1085             mTransmitEvents = true;
   1086         }
   1087     }
   1088 
   1089     private void sendEvent(Event evt) {
   1090 
   1091         if(mTransmitEvents == false) {
   1092             if(V) Log.v(TAG, "mTransmitEvents == false - don't send event.");
   1093             return;
   1094         }
   1095 
   1096         if(D)Log.d(TAG, "sendEvent: " + evt.eventType + " " + evt.handle + " " + evt.folder + " "
   1097                 + evt.oldFolder + " " + evt.msgType.name() + " " + evt.datetime + " "
   1098                 + evt.subject + " " + evt.senderName + " " + evt.priority );
   1099 
   1100         if (mMnsClient == null || mMnsClient.isConnected() == false) {
   1101             Log.d(TAG, "sendEvent: No MNS client registered or connected- don't send event");
   1102             return;
   1103         }
   1104 
   1105         /* Enable use of the cache for checking the filter */
   1106         long eventFilter = mEventFilter;
   1107 
   1108         /* This should have been a switch on the string, but it is not allowed in Java 1.6 */
   1109         /* WARNING: Here we do pointer compare for the string to speed up things, that is.
   1110          * HENCE: always use the EVENT_TYPE_"defines" */
   1111         if(evt.eventType == EVENT_TYPE_NEW) {
   1112             if(!sendEventNewMessage(eventFilter)) {
   1113                 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
   1114                 return;
   1115             }
   1116         } else if(evt.eventType == EVENT_TYPE_DELETE) {
   1117             if(!sendEventMessageDeleted(eventFilter)) {
   1118                 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
   1119                 return;
   1120             }
   1121         } else if(evt.eventType == EVENT_TYPE_REMOVED) {
   1122             if(!sendEventMessageRemoved(eventFilter)) {
   1123                 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
   1124                 return;
   1125             }
   1126         } else if(evt.eventType == EVENT_TYPE_SHIFT) {
   1127             if(!sendEventMessageShift(eventFilter)) {
   1128                 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
   1129                 return;
   1130             }
   1131         } else if(evt.eventType == EVENT_TYPE_DELEVERY_SUCCESS) {
   1132             if(!sendEventDeliverySuccess(eventFilter)) {
   1133                 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
   1134                 return;
   1135             }
   1136         } else if(evt.eventType == EVENT_TYPE_SENDING_SUCCESS) {
   1137             if(!sendEventSendingSuccess(eventFilter)) {
   1138                 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
   1139                 return;
   1140             }
   1141         } else if(evt.eventType == EVENT_TYPE_SENDING_FAILURE) {
   1142             if(!sendEventSendingFailed(eventFilter)) {
   1143                 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
   1144                 return;
   1145             }
   1146         } else if(evt.eventType == EVENT_TYPE_DELIVERY_FAILURE) {
   1147             if(!sendEventDeliveryFailed(eventFilter)) {
   1148                 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
   1149                 return;
   1150             }
   1151         } else if(evt.eventType == EVENT_TYPE_READ_STATUS) {
   1152             if(!sendEventReadStatusChanged(eventFilter)) {
   1153                 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
   1154                 return;
   1155             }
   1156         } else if(evt.eventType == EVENT_TYPE_CONVERSATION) {
   1157             if(!sendEventConversationChanged(eventFilter)) {
   1158                 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
   1159                 return;
   1160             }
   1161         } else if(evt.eventType == EVENT_TYPE_PRESENCE) {
   1162             if(!sendEventParticipantPresenceChanged(eventFilter)) {
   1163                 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
   1164                 return;
   1165             }
   1166         } else if(evt.eventType == EVENT_TYPE_CHAT_STATE) {
   1167             if(!sendEventParticipantChatstateChanged(eventFilter)) {
   1168                 if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
   1169                 return;
   1170             }
   1171         }
   1172 
   1173         try {
   1174             mMnsClient.sendEvent(evt.encode(), mMasId);
   1175         } catch (UnsupportedEncodingException ex) {
   1176             /* do nothing */
   1177             if (D) Log.e(TAG, "Exception - should not happen: ",ex);
   1178         }
   1179     }
   1180 
   1181     private void initMsgList() throws RemoteException {
   1182         if (V) Log.d(TAG, "initMsgList");
   1183 
   1184         if(mEnableSmsMms) {
   1185 
   1186             HashMap<Long, Msg> msgListSms = new HashMap<Long, Msg>();
   1187 
   1188             Cursor c = mResolver.query(Sms.CONTENT_URI,
   1189                     SMS_PROJECTION_SHORT, null, null, null);
   1190             try {
   1191                 if (c != null && c.moveToFirst()) {
   1192                     do {
   1193                         long id = c.getLong(c.getColumnIndex(Sms._ID));
   1194                         int type = c.getInt(c.getColumnIndex(Sms.TYPE));
   1195                         int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
   1196                         int read = c.getInt(c.getColumnIndex(Sms.READ));
   1197 
   1198                         Msg msg = new Msg(id, type, threadId, read);
   1199                         msgListSms.put(id, msg);
   1200                     } while (c.moveToNext());
   1201                 }
   1202             } finally {
   1203                 if (c != null) c.close();
   1204             }
   1205 
   1206             synchronized(getMsgListSms()) {
   1207                 getMsgListSms().clear();
   1208                 setMsgListSms(msgListSms, true); // Set initial folder version counter
   1209             }
   1210 
   1211             HashMap<Long, Msg> msgListMms = new HashMap<Long, Msg>();
   1212 
   1213             c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION_SHORT, null, null, null);
   1214             try {
   1215                 if (c != null && c.moveToFirst()) {
   1216                     do {
   1217                         long id = c.getLong(c.getColumnIndex(Mms._ID));
   1218                         int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
   1219                         int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
   1220                         int read = c.getInt(c.getColumnIndex(Mms.READ));
   1221 
   1222                         Msg msg = new Msg(id, type, threadId, read);
   1223                         msgListMms.put(id, msg);
   1224                     } while (c.moveToNext());
   1225                 }
   1226             } finally {
   1227                 if (c != null) c.close();
   1228             }
   1229 
   1230             synchronized(getMsgListMms()) {
   1231                 getMsgListMms().clear();
   1232                 setMsgListMms(msgListMms, true); // Set initial folder version counter
   1233             }
   1234         }
   1235 
   1236         if(mAccount != null) {
   1237             HashMap<Long, Msg> msgList = new HashMap<Long, Msg>();
   1238             Uri uri = mMessageUri;
   1239             Cursor c = mProviderClient.query(uri, MSG_PROJECTION_SHORT, null, null, null);
   1240 
   1241             try {
   1242                 if (c != null && c.moveToFirst()) {
   1243                     do {
   1244                         long id = c.getLong(c.getColumnIndex(MessageColumns._ID));
   1245                         long folderId = c.getInt(c.getColumnIndex(
   1246                                 BluetoothMapContract.MessageColumns.FOLDER_ID));
   1247                         int readFlag = c.getInt(c.getColumnIndex(
   1248                                 BluetoothMapContract.MessageColumns.FLAG_READ));
   1249                         Msg msg = new Msg(id, folderId, readFlag);
   1250                         msgList.put(id, msg);
   1251                     } while (c.moveToNext());
   1252                 }
   1253             } finally {
   1254                 if (c != null) c.close();
   1255             }
   1256 
   1257             synchronized(getMsgListMsg()) {
   1258                 getMsgListMsg().clear();
   1259                 setMsgListMsg(msgList, true);
   1260             }
   1261         }
   1262     }
   1263 
   1264     private void initContactsList() throws RemoteException {
   1265         if (V) Log.d(TAG, "initContactsList");
   1266         if(mContactUri == null) {
   1267             if (D) Log.d(TAG, "initContactsList() no mContactUri - nothing to init");
   1268             return;
   1269         }
   1270         Uri uri = mContactUri;
   1271         Cursor c = mProviderClient.query(uri,
   1272                 BluetoothMapContract.BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION,
   1273                 null, null, null);
   1274         Map<String, BluetoothMapConvoContactElement> contactList =
   1275                 new HashMap<String, BluetoothMapConvoContactElement>();
   1276         try {
   1277             if (c != null && c.moveToFirst()) {
   1278                 ConvoContactInfo cInfo = new ConvoContactInfo();
   1279                 cInfo.setConvoColunms(c);
   1280                 do {
   1281                     long convoId = c.getLong(cInfo.mContactColConvoId);
   1282                     if (convoId == 0)
   1283                         continue;
   1284                     if (V) BluetoothMapUtils.printCursor(c);
   1285                     String uci = c.getString(cInfo.mContactColUci);
   1286                     String name = c.getString(cInfo.mContactColName);
   1287                     String displayName = c.getString(cInfo.mContactColNickname);
   1288                     String presenceStatus = c.getString(cInfo.mContactColPresenceText);
   1289                     int presenceState = c.getInt(cInfo.mContactColPresenceState);
   1290                     long lastActivity = c.getLong(cInfo.mContactColLastActive);
   1291                     int chatState = c.getInt(cInfo.mContactColChatState);
   1292                     int priority = c.getInt(cInfo.mContactColPriority);
   1293                     String btUid = c.getString(cInfo.mContactColBtUid);
   1294                     BluetoothMapConvoContactElement contact =
   1295                             new BluetoothMapConvoContactElement(uci, name, displayName,
   1296                                     presenceStatus, presenceState, lastActivity, chatState,
   1297                                     priority, btUid);
   1298                     contactList.put(uci, contact);
   1299                 } while (c.moveToNext());
   1300             }
   1301         } finally {
   1302             if (c != null) c.close();
   1303         }
   1304         synchronized(getContactList()) {
   1305             getContactList().clear();
   1306             setContactList(contactList, true);
   1307         }
   1308     }
   1309 
   1310     private void handleMsgListChangesSms() {
   1311         if (V) Log.d(TAG, "handleMsgListChangesSms");
   1312 
   1313         HashMap<Long, Msg> msgListSms = new HashMap<Long, Msg>();
   1314         boolean listChanged = false;
   1315 
   1316         Cursor c;
   1317         if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
   1318             c = mResolver.query(Sms.CONTENT_URI,
   1319                     SMS_PROJECTION_SHORT, null, null, null);
   1320         } else {
   1321             c = mResolver.query(Sms.CONTENT_URI,
   1322                     SMS_PROJECTION_SHORT_EXT, null, null, null);
   1323         }
   1324         synchronized(getMsgListSms()) {
   1325             try {
   1326                 if (c != null && c.moveToFirst()) {
   1327                     do {
   1328                         long id = c.getLong(c.getColumnIndex(Sms._ID));
   1329                         int type = c.getInt(c.getColumnIndex(Sms.TYPE));
   1330                         int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
   1331                         int read = c.getInt(c.getColumnIndex(Sms.READ));
   1332 
   1333                         Msg msg = getMsgListSms().remove(id);
   1334 
   1335                         /* We must filter out any actions made by the MCE, hence do not send e.g.
   1336                          * a message deleted and/or MessageShift for messages deleted by the MCE. */
   1337 
   1338                         if (msg == null) {
   1339                             /* New message */
   1340                             msg = new Msg(id, type, threadId, read);
   1341                             msgListSms.put(id, msg);
   1342                             listChanged = true;
   1343                             Event evt;
   1344                             if (mTransmitEvents == true && // extract contact details only if needed
   1345                                     mMapEventReportVersion >
   1346                             BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
   1347                                 String date = BluetoothMapUtils.getDateTimeString(
   1348                                         c.getLong(c.getColumnIndex(Sms.DATE)));
   1349                                 String subject = c.getString(c.getColumnIndex(Sms.BODY));
   1350                                 String name = "";
   1351                                 String phone = "";
   1352                                 if (type == 1) { //inbox
   1353                                     phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
   1354                                     if (phone != null && !phone.isEmpty()) {
   1355                                         name = BluetoothMapContent.getContactNameFromPhone(phone,
   1356                                                 mResolver);
   1357                                         if(name == null || name.isEmpty()){
   1358                                             name = phone;
   1359                                         }
   1360                                     }else{
   1361                                         name = phone;
   1362                                     }
   1363                                 } else {
   1364                                     TelephonyManager tm =
   1365                                             (TelephonyManager)mContext.getSystemService(
   1366                                             Context.TELEPHONY_SERVICE);
   1367                                     if (tm != null) {
   1368                                         phone = tm.getLine1Number();
   1369                                         name = tm.getLine1AlphaTag();
   1370                                         if(name == null || name.isEmpty()){
   1371                                             name = phone;
   1372                                         }
   1373                                     }
   1374                                 }
   1375                                 String priority = "no";// no priority for sms
   1376                                 /* Incoming message from the network */
   1377                                 if (mMapEventReportVersion ==
   1378                                         BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
   1379                                     evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type),
   1380                                             mSmsType, date, subject, name, priority);
   1381                                 } else {
   1382                                     evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type),
   1383                                             mSmsType, date, subject, name, priority,
   1384                                             (long)threadId, null);
   1385                                 }
   1386                             } else {
   1387                                 /* Incoming message from the network */
   1388                                 evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type),
   1389                                         null, mSmsType);
   1390                             }
   1391                             sendEvent(evt);
   1392                         } else {
   1393                             /* Existing message */
   1394                             if (type != msg.type) {
   1395                                 listChanged = true;
   1396                                 Log.d(TAG, "new type: " + type + " old type: " + msg.type);
   1397                                 String oldFolder = getSmsFolderName(msg.type);
   1398                                 String newFolder = getSmsFolderName(type);
   1399                                 // Filter out the intermediate outbox steps
   1400                                 if(!oldFolder.equalsIgnoreCase(newFolder)) {
   1401                                     Event evt = new Event(EVENT_TYPE_SHIFT, id,
   1402                                             getSmsFolderName(type), oldFolder, mSmsType);
   1403                                     sendEvent(evt);
   1404                                 }
   1405                                 msg.type = type;
   1406                             } else if(threadId != msg.threadId) {
   1407                                 listChanged = true;
   1408                                 Log.d(TAG, "Message delete change: type: " + type
   1409                                         + " old type: " + msg.type
   1410                                         + "\n    threadId: " + threadId
   1411                                         + " old threadId: " + msg.threadId);
   1412                                 if(threadId == DELETED_THREAD_ID) { // Message deleted
   1413                                     // TODO:
   1414                                     // We shall only use the folder attribute, but can't remember
   1415                                     // wether to set it to "deleted" or the name of the folder
   1416                                     // from which the message have been deleted.
   1417                                     // "old_folder" used only for MessageShift event
   1418                                     Event evt = new Event(EVENT_TYPE_DELETE, id,
   1419                                             getSmsFolderName(msg.type), null, mSmsType);
   1420                                     sendEvent(evt);
   1421                                     msg.threadId = threadId;
   1422                                 } else { // Undelete
   1423                                     Event evt = new Event(EVENT_TYPE_SHIFT, id,
   1424                                             getSmsFolderName(msg.type),
   1425                                             BluetoothMapContract.FOLDER_NAME_DELETED, mSmsType);
   1426                                     sendEvent(evt);
   1427                                     msg.threadId = threadId;
   1428                                 }
   1429                             }
   1430                             if(read != msg.flagRead) {
   1431                                 listChanged = true;
   1432                                 msg.flagRead = read;
   1433                                 if (mMapEventReportVersion >
   1434                                         BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
   1435                                     Event evt = new Event(EVENT_TYPE_READ_STATUS, id,
   1436                                             getSmsFolderName(msg.type), mSmsType);
   1437                                     sendEvent(evt);
   1438                                 }
   1439                             }
   1440                             msgListSms.put(id, msg);
   1441                         }
   1442                     } while (c.moveToNext());
   1443                 }
   1444             } finally {
   1445                 if (c != null) c.close();
   1446             }
   1447 
   1448             for (Msg msg : getMsgListSms().values()) {
   1449                 // "old_folder" used only for MessageShift event
   1450                 Event evt = new Event(EVENT_TYPE_DELETE, msg.id,
   1451                         getSmsFolderName(msg.type), null, mSmsType);
   1452                 sendEvent(evt);
   1453                 listChanged = true;
   1454             }
   1455 
   1456             setMsgListSms(msgListSms, listChanged);
   1457         }
   1458     }
   1459 
   1460     private void handleMsgListChangesMms() {
   1461         if (V) Log.d(TAG, "handleMsgListChangesMms");
   1462 
   1463         HashMap<Long, Msg> msgListMms = new HashMap<Long, Msg>();
   1464         boolean listChanged = false;
   1465         Cursor c;
   1466         if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
   1467             c = mResolver.query(Mms.CONTENT_URI,
   1468                     MMS_PROJECTION_SHORT, null, null, null);
   1469         } else {
   1470             c = mResolver.query(Mms.CONTENT_URI,
   1471                     MMS_PROJECTION_SHORT_EXT, null, null, null);
   1472         }
   1473 
   1474         synchronized(getMsgListMms()) {
   1475             try{
   1476                 if (c != null && c.moveToFirst()) {
   1477                     do {
   1478                         long id = c.getLong(c.getColumnIndex(Mms._ID));
   1479                         int type = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
   1480                         int mtype = c.getInt(c.getColumnIndex(Mms.MESSAGE_TYPE));
   1481                         int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
   1482                         // TODO: Go through code to see if we have an issue with mismatch in types
   1483                         //       for threadId. Seems to be a long in DB??
   1484                         int read = c.getInt(c.getColumnIndex(Mms.READ));
   1485 
   1486                         Msg msg = getMsgListMms().remove(id);
   1487 
   1488                         /* We must filter out any actions made by the MCE, hence do not send
   1489                          * e.g. a message deleted and/or MessageShift for messages deleted by the
   1490                          * MCE.*/
   1491 
   1492                         if (msg == null) {
   1493                             /* New message - only notify on retrieve conf */
   1494                             listChanged = true;
   1495                             if (getMmsFolderName(type).equalsIgnoreCase(
   1496                                     BluetoothMapContract.FOLDER_NAME_INBOX) &&
   1497                                     mtype != MESSAGE_TYPE_RETRIEVE_CONF) {
   1498                                 continue;
   1499                             }
   1500                             msg = new Msg(id, type, threadId, read);
   1501                             msgListMms.put(id, msg);
   1502                             Event evt;
   1503                             if (mTransmitEvents == true && // extract contact details only if needed
   1504                                     mMapEventReportVersion !=
   1505                                     BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
   1506                                 String date = BluetoothMapUtils.getDateTimeString(
   1507                                         c.getLong(c.getColumnIndex(Mms.DATE)));
   1508                                 String subject = c.getString(c.getColumnIndex(Mms.SUBJECT));
   1509                                 if (subject == null || subject.length() == 0) {
   1510                                     /* Get subject from mms text body parts - if any exists */
   1511                                     subject = BluetoothMapContent.getTextPartsMms(mResolver, id);
   1512                                 }
   1513                                 int tmpPri = c.getInt(c.getColumnIndex(Mms.PRIORITY));
   1514                                 Log.d(TAG, "TEMP handleMsgListChangesMms, " +
   1515                                         "newMessage 'read' state: " + read +
   1516                                         "priority: " + tmpPri);
   1517 
   1518                                 String address = BluetoothMapContent.getAddressMms(
   1519                                         mResolver,id,BluetoothMapContent.MMS_FROM);
   1520                                 String priority = "no";
   1521                                 if(tmpPri == PduHeaders.PRIORITY_HIGH)
   1522                                     priority = "yes";
   1523 
   1524                                 /* Incoming message from the network */
   1525                                 if (mMapEventReportVersion ==
   1526                                         BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
   1527                                     evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type),
   1528                                             TYPE.MMS, date, subject, address, priority);
   1529                                 } else {
   1530                                     evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type),
   1531                                             TYPE.MMS, date, subject, address, priority,
   1532                                             (long)threadId, null);
   1533                                 }
   1534 
   1535                             } else {
   1536                                 /* Incoming message from the network */
   1537                                 evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type),
   1538                                         null, TYPE.MMS);
   1539                             }
   1540 
   1541                             sendEvent(evt);
   1542                         } else {
   1543                             /* Existing message */
   1544                             if (type != msg.type) {
   1545                                 Log.d(TAG, "new type: " + type + " old type: " + msg.type);
   1546                                 Event evt;
   1547                                 listChanged = true;
   1548                                 if(msg.localInitiatedSend == false) {
   1549                                     // Only send events about local initiated changes
   1550                                     evt = new Event(EVENT_TYPE_SHIFT, id, getMmsFolderName(type),
   1551                                             getMmsFolderName(msg.type), TYPE.MMS);
   1552                                     sendEvent(evt);
   1553                                 }
   1554                                 msg.type = type;
   1555 
   1556                                 if (getMmsFolderName(type).equalsIgnoreCase(
   1557                                         BluetoothMapContract.FOLDER_NAME_SENT)
   1558                                         && msg.localInitiatedSend == true) {
   1559                                     // Stop tracking changes for this message
   1560                                     msg.localInitiatedSend = false;
   1561                                     evt = new Event(EVENT_TYPE_SENDING_SUCCESS, id,
   1562                                             getMmsFolderName(type), null, TYPE.MMS);
   1563                                     sendEvent(evt);
   1564                                 }
   1565                             } else if(threadId != msg.threadId) {
   1566                                 Log.d(TAG, "Message delete change: type: " + type + " old type: "
   1567                                         + msg.type
   1568                                         + "\n    threadId: " + threadId + " old threadId: "
   1569                                         + msg.threadId);
   1570                                 listChanged = true;
   1571                                 if(threadId == DELETED_THREAD_ID) { // Message deleted
   1572                                     // "old_folder" used only for MessageShift event
   1573                                     Event evt = new Event(EVENT_TYPE_DELETE, id,
   1574                                             getMmsFolderName(msg.type), null, TYPE.MMS);
   1575                                     sendEvent(evt);
   1576                                     msg.threadId = threadId;
   1577                                 } else { // Undelete
   1578                                     Event evt = new Event(EVENT_TYPE_SHIFT, id,
   1579                                             getMmsFolderName(msg.type),
   1580                                             BluetoothMapContract.FOLDER_NAME_DELETED, TYPE.MMS);
   1581                                     sendEvent(evt);
   1582                                     msg.threadId = threadId;
   1583                                 }
   1584                             }
   1585                             if(read != msg.flagRead) {
   1586                                 listChanged = true;
   1587                                 msg.flagRead = read;
   1588                                 if (mMapEventReportVersion >
   1589                                         BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
   1590                                     Event evt = new Event(EVENT_TYPE_READ_STATUS, id,
   1591                                             getMmsFolderName(msg.type), TYPE.MMS);
   1592                                     sendEvent(evt);
   1593                                 }
   1594                             }
   1595                             msgListMms.put(id, msg);
   1596                         }
   1597                     } while (c.moveToNext());
   1598 
   1599                 }
   1600             } finally {
   1601                 if (c != null) c.close();
   1602             }
   1603             for (Msg msg : getMsgListMms().values()) {
   1604                 // "old_folder" used only for MessageShift event
   1605                 Event evt = new Event(EVENT_TYPE_DELETE, msg.id,
   1606                         getMmsFolderName(msg.type), null, TYPE.MMS);
   1607                 sendEvent(evt);
   1608                 listChanged = true;
   1609             }
   1610             setMsgListMms(msgListMms, listChanged);
   1611         }
   1612     }
   1613 
   1614     private void handleMsgListChangesMsg(Uri uri)  throws RemoteException{
   1615         if (V) Log.v(TAG, "handleMsgListChangesMsg uri: " + uri.toString());
   1616 
   1617         // TODO: Change observer to handle accountId and message ID if present
   1618 
   1619         HashMap<Long, Msg> msgList = new HashMap<Long, Msg>();
   1620         Cursor c;
   1621         boolean listChanged = false;
   1622         if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
   1623             c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT, null, null, null);
   1624         } else if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
   1625             c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT_EXT, null, null, null);
   1626         } else {
   1627             c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT_EXT2, null, null, null);
   1628         }
   1629         synchronized(getMsgListMsg()) {
   1630             try {
   1631                 if (c != null && c.moveToFirst()) {
   1632                     do {
   1633                         long id = c.getLong(c.getColumnIndex(
   1634                                 BluetoothMapContract.MessageColumns._ID));
   1635                         int folderId = c.getInt(c.getColumnIndex(
   1636                                 BluetoothMapContract.MessageColumns.FOLDER_ID));
   1637                         int readFlag = c.getInt(c.getColumnIndex(
   1638                                 BluetoothMapContract.MessageColumns.FLAG_READ));
   1639                         Msg msg = getMsgListMsg().remove(id);
   1640                         BluetoothMapFolderElement folderElement = mFolders.getFolderById(folderId);
   1641                         String newFolder;
   1642                         if(folderElement != null) {
   1643                             newFolder = folderElement.getFullPath();
   1644                         } else {
   1645                             // This can happen if a new folder is created while connected
   1646                             newFolder = "unknown";
   1647                         }
   1648                         /* We must filter out any actions made by the MCE, hence do not send e.g.
   1649                          * a message deleted and/or MessageShift for messages deleted by the MCE. */
   1650                         if (msg == null) {
   1651                             listChanged = true;
   1652                             /* New message - created with message unread */
   1653                             msg = new Msg(id, folderId, 0, readFlag);
   1654                             msgList.put(id, msg);
   1655                             Event evt;
   1656                             /* Incoming message from the network */
   1657                             if (mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
   1658                                 String date = BluetoothMapUtils.getDateTimeString(
   1659                                         c.getLong(c.getColumnIndex(
   1660                                                 BluetoothMapContract.MessageColumns.DATE)));
   1661                                 String subject = c.getString(c.getColumnIndex(
   1662                                         BluetoothMapContract.MessageColumns.SUBJECT));
   1663                                 String address = c.getString(c.getColumnIndex(
   1664                                         BluetoothMapContract.MessageColumns.FROM_LIST));
   1665                                 String priority = "no";
   1666                                 if(c.getInt(c.getColumnIndex(
   1667                                         BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY))
   1668                                         == 1)
   1669                                     priority = "yes";
   1670                                 if (mMapEventReportVersion ==
   1671                                         BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
   1672                                     evt = new Event(EVENT_TYPE_NEW, id, newFolder,
   1673                                             mAccount.getType(), date, subject, address, priority);
   1674                                 } else {
   1675                                     long thread_id = c.getLong(c.getColumnIndex(
   1676                                             BluetoothMapContract.MessageColumns.THREAD_ID));
   1677                                     String thread_name = c.getString(c.getColumnIndex(
   1678                                             BluetoothMapContract.MessageColumns.THREAD_NAME));
   1679                                     evt = new Event(EVENT_TYPE_NEW, id, newFolder,
   1680                                             mAccount.getType(), date, subject, address, priority,
   1681                                             thread_id, thread_name);
   1682                                 }
   1683                             } else {
   1684                                 evt = new Event(EVENT_TYPE_NEW, id, newFolder, null, TYPE.EMAIL);
   1685                             }
   1686                             sendEvent(evt);
   1687                         } else {
   1688                             /* Existing message */
   1689                             if (folderId != msg.folderId && msg.folderId != -1) {
   1690                                 if (D) Log.d(TAG, "new folderId: " + folderId + " old folderId: "
   1691                                         + msg.folderId);
   1692                                 BluetoothMapFolderElement oldFolderElement =
   1693                                         mFolders.getFolderById(msg.folderId);
   1694                                 String oldFolder;
   1695                                 listChanged = true;
   1696                                 if(oldFolderElement != null) {
   1697                                     oldFolder = oldFolderElement.getFullPath();
   1698                                 } else {
   1699                                     // This can happen if a new folder is created while connected
   1700                                     oldFolder = "unknown";
   1701                                 }
   1702                                 BluetoothMapFolderElement deletedFolder =
   1703                                         mFolders.getFolderByName(
   1704                                                 BluetoothMapContract.FOLDER_NAME_DELETED);
   1705                                 BluetoothMapFolderElement sentFolder =
   1706                                         mFolders.getFolderByName(
   1707                                                 BluetoothMapContract.FOLDER_NAME_SENT);
   1708                                 /*
   1709                                  *  If the folder is now 'deleted', send a deleted-event in stead of
   1710                                  *  a shift or if message is sent initiated by MAP Client, then send
   1711                                  *  sending-success otherwise send folderShift
   1712                                  */
   1713                                 if(deletedFolder != null && deletedFolder.getFolderId()
   1714                                         == folderId) {
   1715                                     // "old_folder" used only for MessageShift event
   1716                                     Event evt = new Event(EVENT_TYPE_DELETE, msg.id, oldFolder,
   1717                                             null, mAccount.getType());
   1718                                     sendEvent(evt);
   1719                                 } else if(sentFolder != null
   1720                                         && sentFolder.getFolderId() == folderId
   1721                                         && msg.localInitiatedSend == true) {
   1722                                     if(msg.transparent) {
   1723                                         mResolver.delete(
   1724                                                 ContentUris.withAppendedId(mMessageUri, id),
   1725                                                 null, null);
   1726                                     } else {
   1727                                         msg.localInitiatedSend = false;
   1728                                         Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msg.id,
   1729                                                 oldFolder, null, mAccount.getType());
   1730                                         sendEvent(evt);
   1731                                     }
   1732                                 } else {
   1733                                     if (!oldFolder.equalsIgnoreCase("root")) {
   1734                                         Event evt = new Event(EVENT_TYPE_SHIFT, id, newFolder,
   1735                                                 oldFolder, mAccount.getType());
   1736                                         sendEvent(evt);
   1737                                     }
   1738                                 }
   1739                                 msg.folderId = folderId;
   1740                             }
   1741                             if(readFlag != msg.flagRead) {
   1742                                 listChanged = true;
   1743 
   1744                                 if (mMapEventReportVersion >
   1745                                 BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
   1746                                     Event evt = new Event(EVENT_TYPE_READ_STATUS, id, newFolder,
   1747                                             mAccount.getType());
   1748                                     sendEvent(evt);
   1749                                     msg.flagRead = readFlag;
   1750                                 }
   1751                             }
   1752 
   1753                             msgList.put(id, msg);
   1754                         }
   1755                     } while (c.moveToNext());
   1756                 }
   1757             } finally {
   1758                 if (c != null) c.close();
   1759             }
   1760             // For all messages no longer in the database send a delete notification
   1761             for (Msg msg : getMsgListMsg().values()) {
   1762                 BluetoothMapFolderElement oldFolderElement = mFolders.getFolderById(msg.folderId);
   1763                 String oldFolder;
   1764                 listChanged = true;
   1765                 if(oldFolderElement != null) {
   1766                     oldFolder = oldFolderElement.getFullPath();
   1767                 } else {
   1768                     oldFolder = "unknown";
   1769                 }
   1770                 /* Some e-mail clients delete the message after sending, and creates a
   1771                  * new message in sent. We cannot track the message anymore, hence send both a
   1772                  * send success and delete message.
   1773                  */
   1774                 if(msg.localInitiatedSend == true) {
   1775                     msg.localInitiatedSend = false;
   1776                     // If message is send with transparency don't set folder as message is deleted
   1777                     if (msg.transparent)
   1778                         oldFolder = null;
   1779                     Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msg.id, oldFolder, null,
   1780                             mAccount.getType());
   1781                     sendEvent(evt);
   1782                 }
   1783                 /* As this message deleted is only send on a real delete - don't set folder.
   1784                  *  - only send delete event if message is not sent with transparency
   1785                  */
   1786                 if (!msg.transparent) {
   1787 
   1788                     // "old_folder" used only for MessageShift event
   1789                     Event evt = new Event(EVENT_TYPE_DELETE, msg.id, oldFolder,
   1790                             null, mAccount.getType());
   1791                     sendEvent(evt);
   1792                 }
   1793             }
   1794             setMsgListMsg(msgList, listChanged);
   1795         }
   1796     }
   1797 
   1798     private void handleMsgListChanges(Uri uri) {
   1799         if(uri.getAuthority().equals(mAuthority)) {
   1800             try {
   1801                 if(D) Log.d(TAG, "handleMsgListChanges: account type = "
   1802                         + mAccount.getType().toString());
   1803                 handleMsgListChangesMsg(uri);
   1804             } catch(RemoteException e) {
   1805                 mMasInstance.restartObexServerSession();
   1806                 Log.w(TAG, "Problems contacting the ContentProvider in mas Instance "
   1807                         + mMasId + " restaring ObexServerSession");
   1808             }
   1809 
   1810         }
   1811         // TODO: check to see if there could be problem with IM and SMS in one instance
   1812         if (mEnableSmsMms) {
   1813             handleMsgListChangesSms();
   1814             handleMsgListChangesMms();
   1815         }
   1816     }
   1817 
   1818     private void handleContactListChanges(Uri uri) {
   1819         if (uri.getAuthority().equals(mAuthority)) {
   1820             try {
   1821                 if (V) Log.v(TAG,"handleContactListChanges uri: " + uri.toString());
   1822                 Cursor c = null;
   1823                 boolean listChanged = false;
   1824                 try {
   1825                     ConvoContactInfo cInfo = new ConvoContactInfo();
   1826 
   1827                     if (mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V10
   1828                             && mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
   1829                         c = mProviderClient
   1830                                 .query(mContactUri,
   1831                                         BluetoothMapContract.
   1832                                         BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION,
   1833                                         null, null, null);
   1834                         cInfo.setConvoColunms(c);
   1835                     } else {
   1836                         if (V) Log.v(TAG,"handleContactListChanges MAP version does not" +
   1837                                 "support convocontact notifications");
   1838                         return;
   1839                     }
   1840 
   1841                     HashMap<String, BluetoothMapConvoContactElement> contactList =
   1842                             new HashMap<String,
   1843                             BluetoothMapConvoContactElement>(getContactList().size());
   1844 
   1845                     synchronized (getContactList()) {
   1846                         if (c != null && c.moveToFirst()) {
   1847                             do {
   1848                                 String uci = c.getString(cInfo.mContactColUci);
   1849                                 long convoId = c.getLong(cInfo.mContactColConvoId);
   1850                                 if (convoId == 0)
   1851                                     continue;
   1852 
   1853                                 if (V) BluetoothMapUtils.printCursor(c);
   1854 
   1855                                 BluetoothMapConvoContactElement contact =
   1856                                         getContactList().remove(uci);
   1857 
   1858                                 /*
   1859                                  * We must filter out any actions made by the
   1860                                  * MCE, hence do not send e.g. a message deleted
   1861                                  * and/or MessageShift for messages deleted by
   1862                                  * the MCE.
   1863                                  */
   1864                                 if (contact == null) {
   1865                                     listChanged = true;
   1866                                     /*
   1867                                      * New contact - added to conversation and
   1868                                      * tracked here
   1869                                      */
   1870                                     if (mMapEventReportVersion
   1871                                             != BluetoothMapUtils.MAP_EVENT_REPORT_V10
   1872                                             && mMapEventReportVersion
   1873                                             != BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
   1874                                         Event evt;
   1875                                         String name = c
   1876                                                 .getString(cInfo.mContactColName);
   1877                                         String displayName = c
   1878                                                 .getString(cInfo.mContactColNickname);
   1879                                         String presenceStatus = c
   1880                                                 .getString(cInfo.mContactColPresenceText);
   1881                                         int presenceState = c
   1882                                                 .getInt(cInfo.mContactColPresenceState);
   1883                                         long lastActivity = c
   1884                                                 .getLong(cInfo.mContactColLastActive);
   1885                                         int chatState = c
   1886                                                 .getInt(cInfo.mContactColChatState);
   1887                                         int priority = c
   1888                                                 .getInt(cInfo.mContactColPriority);
   1889                                         String btUid = c
   1890                                                 .getString(cInfo.mContactColBtUid);
   1891 
   1892                                         // Get Conversation information for
   1893                                         // event
   1894 //                                        Uri convoUri = Uri
   1895 //                                                .parse(mAccount.mBase_uri
   1896 //                                                        + "/"
   1897 //                                                        + BluetoothMapContract.TABLE_CONVERSATION);
   1898 //                                        String whereClause = "contacts._id = "
   1899 //                                                + convoId;
   1900 //                                        Cursor cConvo = mProviderClient
   1901 //                                                .query(convoUri,
   1902 //                                                       BluetoothMapContract.BT_CONVERSATION_PROJECTION,
   1903 //                                                       whereClause, null, null);
   1904                                         // TODO: will move out of the loop when merged with CB's
   1905                                         // changes make sure to look up col index out side loop
   1906                                         String convoName = null;
   1907 //                                        if (cConvo != null
   1908 //                                                && cConvo.moveToFirst()) {
   1909 //                                            convoName = cConvo
   1910 //                                                    .getString(cConvo
   1911 //                                                            .getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME));
   1912 //                                        }
   1913 
   1914                                         contact = new BluetoothMapConvoContactElement(
   1915                                                 uci, name, displayName,
   1916                                                 presenceStatus, presenceState,
   1917                                                 lastActivity, chatState,
   1918                                                 priority, btUid);
   1919 
   1920                                         contactList.put(uci, contact);
   1921 
   1922                                         evt = new Event(
   1923                                                 EVENT_TYPE_CONVERSATION,
   1924                                                 uci,
   1925                                                 mAccount.getType(),
   1926                                                 name,
   1927                                                 String.valueOf(priority),
   1928                                                 BluetoothMapUtils
   1929                                                 .getDateTimeString(lastActivity),
   1930                                                 convoId, convoName,
   1931                                                 presenceState, presenceStatus,
   1932                                                 chatState);
   1933 
   1934                                         sendEvent(evt);
   1935                                     }
   1936 
   1937                                 } else {
   1938                                     // Not new - compare updated content
   1939 //                                    Uri convoUri = Uri
   1940 //                                            .parse(mAccount.mBase_uri
   1941 //                                                    + "/"
   1942 //                                                    + BluetoothMapContract.TABLE_CONVERSATION);
   1943                                     // TODO: Should be changed to own provider interface name
   1944 //                                    String whereClause = "contacts._id = "
   1945 //                                            + convoId;
   1946 //                                    Cursor cConvo = mProviderClient
   1947 //                                            .query(convoUri,
   1948 //                                                    BluetoothMapContract.BT_CONVERSATION_PROJECTION,
   1949 //                                                    whereClause, null, null);
   1950 //                                    // TODO: will move out of the loop when merged with CB's
   1951 //                                    // changes make sure to look up col index out side loop
   1952                                     String convoName = null;
   1953 //                                    if (cConvo != null && cConvo.moveToFirst()) {
   1954 //                                        convoName = cConvo
   1955 //                                                .getString(cConvo
   1956 //                                                        .getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME));
   1957 //                                    }
   1958 
   1959                                     // Check if presence is updated
   1960                                     int presenceState = c.getInt(cInfo.mContactColPresenceState);
   1961                                     String presenceStatus = c.getString(
   1962                                             cInfo.mContactColPresenceText);
   1963                                     String currentPresenceStatus = contact
   1964                                             .getPresenceStatus();
   1965                                     if (contact.getPresenceAvailability() != presenceState
   1966                                             || currentPresenceStatus != presenceStatus) {
   1967                                         long lastOnline = c
   1968                                                 .getLong(cInfo.mContactColLastOnline);
   1969                                         contact.setPresenceAvailability(presenceState);
   1970                                         contact.setLastActivity(lastOnline);
   1971                                         if (currentPresenceStatus != null
   1972                                                 && !currentPresenceStatus
   1973                                                 .equals(presenceStatus)) {
   1974                                             contact.setPresenceStatus(presenceStatus);
   1975                                         }
   1976                                         Event evt = new Event(
   1977                                                 EVENT_TYPE_PRESENCE,
   1978                                                 uci,
   1979                                                 mAccount.getType(),
   1980                                                 contact.getName(),
   1981                                                 String.valueOf(contact
   1982                                                         .getPriority()),
   1983                                                         BluetoothMapUtils
   1984                                                         .getDateTimeString(lastOnline),
   1985                                                         convoId, convoName,
   1986                                                         presenceState, presenceStatus,
   1987                                                         0);
   1988                                         sendEvent(evt);
   1989                                     }
   1990 
   1991                                     // Check if chat state is updated
   1992                                     int chatState = c.getInt(cInfo.mContactColChatState);
   1993                                     if (contact.getChatState() != chatState) {
   1994                                         // Get DB timestamp
   1995                                         long lastActivity = c.getLong(cInfo.mContactColLastActive);
   1996                                         contact.setLastActivity(lastActivity);
   1997                                         contact.setChatState(chatState);
   1998                                         Event evt = new Event(
   1999                                                 EVENT_TYPE_CHAT_STATE,
   2000                                                 uci,
   2001                                                 mAccount.getType(),
   2002                                                 contact.getName(),
   2003                                                 String.valueOf(contact
   2004                                                         .getPriority()),
   2005                                                         BluetoothMapUtils
   2006                                                         .getDateTimeString(lastActivity),
   2007                                                         convoId, convoName, 0, null,
   2008                                                         chatState);
   2009                                         sendEvent(evt);
   2010                                     }
   2011                                     contactList.put(uci, contact);
   2012                                 }
   2013                             } while (c.moveToNext());
   2014                         }
   2015                         if(getContactList().size() > 0) {
   2016                             // one or more contacts were deleted, hence the conversation listing
   2017                             // version counter should change.
   2018                             listChanged = true;
   2019                         }
   2020                         setContactList(contactList, listChanged);
   2021                     } // end synchronized
   2022                 } finally {
   2023                     if (c != null) c.close();
   2024                 }
   2025             } catch (RemoteException e) {
   2026                 mMasInstance.restartObexServerSession();
   2027                 Log.w(TAG,
   2028                         "Problems contacting the ContentProvider in mas Instance "
   2029                                 + mMasId + " restaring ObexServerSession");
   2030             }
   2031 
   2032         }
   2033         // TODO: conversation contact updates if IM and SMS(MMS in one instance
   2034     }
   2035 
   2036     private boolean setEmailMessageStatusDelete(BluetoothMapFolderElement mCurrentFolder,
   2037             String uriStr, long handle, int status) {
   2038         boolean res = false;
   2039         Uri uri = Uri.parse(uriStr + BluetoothMapContract.TABLE_MESSAGE);
   2040 
   2041         int updateCount = 0;
   2042         ContentValues contentValues = new ContentValues();
   2043         BluetoothMapFolderElement deleteFolder = mFolders.
   2044                 getFolderByName(BluetoothMapContract.FOLDER_NAME_DELETED);
   2045         contentValues.put(BluetoothMapContract.MessageColumns._ID, handle);
   2046         synchronized(getMsgListMsg()) {
   2047             Msg msg = getMsgListMsg().get(handle);
   2048             if (status == BluetoothMapAppParams.STATUS_VALUE_YES) {
   2049                 /* Set deleted folder id */
   2050                 long folderId = -1;
   2051                 if(deleteFolder != null) {
   2052                     folderId = deleteFolder.getFolderId();
   2053                 }
   2054                 contentValues.put(BluetoothMapContract.MessageColumns.FOLDER_ID,folderId);
   2055                 updateCount = mResolver.update(uri, contentValues, null, null);
   2056                 /* The race between updating the value in our cached values and the database
   2057                  * is handled by the synchronized statement. */
   2058                 if(updateCount > 0) {
   2059                     res = true;
   2060                     if (msg != null) {
   2061                         msg.oldFolderId = msg.folderId;
   2062                         /* Update the folder ID to avoid triggering an event for MCE
   2063                          * initiated actions. */
   2064                         msg.folderId = folderId;
   2065                     }
   2066                     if(D) Log.d(TAG, "Deleted MSG: " + handle + " from folderId: " + folderId);
   2067                 } else {
   2068                     Log.w(TAG, "Msg: " + handle + " - Set delete status " + status
   2069                             + " failed for folderId " + folderId);
   2070                 }
   2071             } else if (status == BluetoothMapAppParams.STATUS_VALUE_NO) {
   2072                 /* Undelete message. move to old folder if we know it,
   2073                  * else move to inbox - as dictated by the spec. */
   2074                 if(msg != null && deleteFolder != null &&
   2075                         msg.folderId == deleteFolder.getFolderId()) {
   2076                     /* Only modify messages in the 'Deleted' folder */
   2077                     long folderId = -1;
   2078                     BluetoothMapFolderElement inboxFolder = mCurrentFolder.
   2079                             getFolderByName(BluetoothMapContract.FOLDER_NAME_INBOX);
   2080                     if (msg != null && msg.oldFolderId != -1) {
   2081                         folderId = msg.oldFolderId;
   2082                     } else {
   2083                         if(inboxFolder != null) {
   2084                             folderId = inboxFolder.getFolderId();
   2085                         }
   2086                         if(D)Log.d(TAG,"We did not delete the message, hence the old folder " +
   2087                                 "is unknown. Moving to inbox.");
   2088                     }
   2089                     contentValues.put(BluetoothMapContract.MessageColumns.FOLDER_ID, folderId);
   2090                     updateCount = mResolver.update(uri, contentValues, null, null);
   2091                     if(updateCount > 0) {
   2092                         res = true;
   2093                         /* Update the folder ID to avoid triggering an event for MCE
   2094                          * initiated actions. */
   2095                         /* UPDATE: Actually the BT-Spec. states that an undelete is a move of the
   2096                          * message to INBOX - clearified in errata 5591.
   2097                          * Therefore we update the cache to INBOX-folderId - to trigger a message
   2098                          * shift event to the old-folder. */
   2099                         if(inboxFolder != null) {
   2100                             msg.folderId = inboxFolder.getFolderId();
   2101                         } else {
   2102                             msg.folderId = folderId;
   2103                         }
   2104                     } else {
   2105                         if(D)Log.d(TAG,"We did not delete the message, hence the old folder " +
   2106                                 "is unknown. Moving to inbox.");
   2107                     }
   2108                 }
   2109             }
   2110             if(V) {
   2111                 BluetoothMapFolderElement folderElement;
   2112                 String folderName = "unknown";
   2113                 if (msg != null) {
   2114                     folderElement = mCurrentFolder.getFolderById(msg.folderId);
   2115                     if(folderElement != null) {
   2116                         folderName = folderElement.getName();
   2117                     }
   2118                 }
   2119                 Log.d(TAG,"setEmailMessageStatusDelete: " + handle + " from " + folderName
   2120                         + " status: " + status);
   2121             }
   2122         }
   2123         if(res == false) {
   2124             Log.w(TAG, "Set delete status " + status + " failed.");
   2125         }
   2126         return res;
   2127     }
   2128 
   2129     private void updateThreadId(Uri uri, String valueString, long threadId) {
   2130         ContentValues contentValues = new ContentValues();
   2131         contentValues.put(valueString, threadId);
   2132         mResolver.update(uri, contentValues, null, null);
   2133     }
   2134 
   2135     private boolean deleteMessageMms(long handle) {
   2136         boolean res = false;
   2137         Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
   2138         Cursor c = mResolver.query(uri, null, null, null, null);
   2139         try {
   2140             if (c != null && c.moveToFirst()) {
   2141                 /* Move to deleted folder, or delete if already in deleted folder */
   2142                 int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
   2143                 if (threadId != DELETED_THREAD_ID) {
   2144                     /* Set deleted thread id */
   2145                     synchronized(getMsgListMms()) {
   2146                         Msg msg = getMsgListMms().get(handle);
   2147                         if(msg != null) { // This will always be the case
   2148                             msg.threadId = DELETED_THREAD_ID;
   2149                         }
   2150                     }
   2151                     updateThreadId(uri, Mms.THREAD_ID, DELETED_THREAD_ID);
   2152                 } else {
   2153                     /* Delete from observer message list to avoid delete notifications */
   2154                     synchronized(getMsgListMms()) {
   2155                         getMsgListMms().remove(handle);
   2156                     }
   2157                     /* Delete message */
   2158                     mResolver.delete(uri, null, null);
   2159                 }
   2160                 res = true;
   2161             }
   2162         } finally {
   2163             if (c != null) c.close();
   2164         }
   2165 
   2166         return res;
   2167     }
   2168 
   2169     private boolean unDeleteMessageMms(long handle) {
   2170         boolean res = false;
   2171         Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
   2172         Cursor c = mResolver.query(uri, null, null, null, null);
   2173         try {
   2174             if (c != null && c.moveToFirst()) {
   2175                 int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
   2176                 if (threadId == DELETED_THREAD_ID) {
   2177                     /* Restore thread id from address, or if no thread for address
   2178                      * create new thread by insert and remove of fake message */
   2179                     String address;
   2180                     long id = c.getLong(c.getColumnIndex(Mms._ID));
   2181                     int msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
   2182                     if (msgBox == Mms.MESSAGE_BOX_INBOX) {
   2183                         address = BluetoothMapContent.getAddressMms(mResolver, id,
   2184                                 BluetoothMapContent.MMS_FROM);
   2185                     } else {
   2186                         address = BluetoothMapContent.getAddressMms(mResolver, id,
   2187                                 BluetoothMapContent.MMS_TO);
   2188                     }
   2189                     Set<String> recipients = new HashSet<String>();
   2190                     recipients.addAll(Arrays.asList(address));
   2191                     Long oldThreadId = Telephony.Threads.getOrCreateThreadId(mContext, recipients);
   2192                     synchronized(getMsgListMms()) {
   2193                         Msg msg = getMsgListMms().get(handle);
   2194                         if(msg != null) { // This will always be the case
   2195                             msg.threadId = oldThreadId.intValue();
   2196                             // Spec. states that undelete shall shift the message to Inbox.
   2197                             // Hence we need to trigger a message shift from INBOX to old-folder
   2198                             // after undelete.
   2199                             // We do this by changing the cached folder value to being inbox - hence
   2200                             // the event handler will se the update as the message have been shifted
   2201                             // from INBOX to old-folder. (Errata 5591 clearifies this)
   2202                             msg.type = Mms.MESSAGE_BOX_INBOX;
   2203                         }
   2204                     }
   2205                     updateThreadId(uri, Mms.THREAD_ID, oldThreadId);
   2206                 } else {
   2207                     Log.d(TAG, "Message not in deleted folder: handle " + handle
   2208                             + " threadId " + threadId);
   2209                 }
   2210                 res = true;
   2211             }
   2212         } finally {
   2213             if (c != null) c.close();
   2214         }
   2215         return res;
   2216     }
   2217 
   2218     private boolean deleteMessageSms(long handle) {
   2219         boolean res = false;
   2220         Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle);
   2221         Cursor c = mResolver.query(uri, null, null, null, null);
   2222         try {
   2223             if (c != null && c.moveToFirst()) {
   2224                 /* Move to deleted folder, or delete if already in deleted folder */
   2225                 int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
   2226                 if (threadId != DELETED_THREAD_ID) {
   2227                     synchronized(getMsgListSms()) {
   2228                         Msg msg = getMsgListSms().get(handle);
   2229                         if(msg != null) { // This will always be the case
   2230                             msg.threadId = DELETED_THREAD_ID;
   2231                         }
   2232                     }
   2233                     /* Set deleted thread id */
   2234                     updateThreadId(uri, Sms.THREAD_ID, DELETED_THREAD_ID);
   2235                 } else {
   2236                     /* Delete from observer message list to avoid delete notifications */
   2237                     synchronized(getMsgListSms()) {
   2238                         getMsgListSms().remove(handle);
   2239                     }
   2240                     /* Delete message */
   2241                     mResolver.delete(uri, null, null);
   2242                 }
   2243                 res = true;
   2244             }
   2245         } finally {
   2246             if (c != null) c.close();
   2247         }
   2248         return res;
   2249     }
   2250 
   2251     private boolean unDeleteMessageSms(long handle) {
   2252         boolean res = false;
   2253         Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle);
   2254         Cursor c = mResolver.query(uri, null, null, null, null);
   2255         try {
   2256             if (c != null && c.moveToFirst()) {
   2257                 int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
   2258                 if (threadId == DELETED_THREAD_ID) {
   2259                     String address = c.getString(c.getColumnIndex(Sms.ADDRESS));
   2260                     Set<String> recipients = new HashSet<String>();
   2261                     recipients.addAll(Arrays.asList(address));
   2262                     Long oldThreadId = Telephony.Threads.getOrCreateThreadId(mContext, recipients);
   2263                     synchronized(getMsgListSms()) {
   2264                         Msg msg = getMsgListSms().get(handle);
   2265                         if(msg != null) {
   2266                             msg.threadId = oldThreadId.intValue();
   2267                             /* This will always be the case
   2268                              * The threadId is specified as an int, so it is safe to truncate
   2269                              * TODO: Test that this will trigger a message-shift from Inbox
   2270                              * to old-folder
   2271                              **/
   2272                             /* Spec. states that undelete shall shift the message to Inbox.
   2273                              * Hence we need to trigger a message shift from INBOX to old-folder
   2274                              * after undelete.
   2275                              * We do this by changing the cached folder value to being inbox - hence
   2276                              * the event handler will se the update as the message have been shifted
   2277                              * from INBOX to old-folder. (Errata 5591 clearifies this)
   2278                              * */
   2279                             msg.type = Sms.MESSAGE_TYPE_INBOX;
   2280                         }
   2281                     }
   2282                     updateThreadId(uri, Sms.THREAD_ID, oldThreadId);
   2283                 } else {
   2284                     Log.d(TAG, "Message not in deleted folder: handle " + handle
   2285                             + " threadId " + threadId);
   2286                 }
   2287                 res = true;
   2288             }
   2289         } finally {
   2290             if (c != null) c.close();
   2291         }
   2292         return res;
   2293     }
   2294 
   2295     /**
   2296      *
   2297      * @param handle
   2298      * @param type
   2299      * @param mCurrentFolder
   2300      * @param uriStr
   2301      * @param statusValue
   2302      * @return true is success
   2303      */
   2304     public boolean setMessageStatusDeleted(long handle, TYPE type,
   2305             BluetoothMapFolderElement mCurrentFolder, String uriStr, int statusValue) {
   2306         boolean res = false;
   2307         if (D) Log.d(TAG, "setMessageStatusDeleted: handle " + handle
   2308                 + " type " + type + " value " + statusValue);
   2309 
   2310         if (type == TYPE.EMAIL) {
   2311             res = setEmailMessageStatusDelete(mCurrentFolder, uriStr, handle, statusValue);
   2312         } else if (type == TYPE.IM) {
   2313             // TODO: to do when deleting IM message
   2314             if (D) Log.d(TAG, "setMessageStatusDeleted: IM not handled" );
   2315         } else {
   2316             if (statusValue == BluetoothMapAppParams.STATUS_VALUE_YES) {
   2317                 if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) {
   2318                     res = deleteMessageSms(handle);
   2319                 } else if (type == TYPE.MMS) {
   2320                     res = deleteMessageMms(handle);
   2321                 }
   2322             } else if (statusValue == BluetoothMapAppParams.STATUS_VALUE_NO) {
   2323                 if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) {
   2324                     res = unDeleteMessageSms(handle);
   2325                 } else if (type == TYPE.MMS) {
   2326                     res = unDeleteMessageMms(handle);
   2327                 }
   2328             }
   2329         }
   2330         return res;
   2331     }
   2332 
   2333     /**
   2334      *
   2335      * @param handle
   2336      * @param type
   2337      * @param uriStr
   2338      * @param statusValue
   2339      * @return true at success
   2340      */
   2341     public boolean setMessageStatusRead(long handle, TYPE type, String uriStr, int statusValue)
   2342             throws RemoteException{
   2343         int count = 0;
   2344 
   2345         if (D) Log.d(TAG, "setMessageStatusRead: handle " + handle
   2346                 + " type " + type + " value " + statusValue);
   2347 
   2348         /* Approved MAP spec errata 3445 states that read status initiated
   2349          * by the MCE shall change the MSE read status. */
   2350         if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) {
   2351             Uri uri = Sms.Inbox.CONTENT_URI;
   2352             ContentValues contentValues = new ContentValues();
   2353             contentValues.put(Sms.READ, statusValue);
   2354             contentValues.put(Sms.SEEN, statusValue);
   2355             String where = Sms._ID+"="+handle;
   2356             String values = contentValues.toString();
   2357             if (D) Log.d(TAG, " -> SMS Uri: " + uri.toString() +
   2358                     " Where " + where + " 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, where, 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         Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
   3338                 null);
   3339         try {
   3340             if (c != null && c.moveToFirst()) {
   3341                 do {
   3342                     long id = c.getLong(c.getColumnIndex(Sms._ID));
   3343                     String msgBody = c.getString(c.getColumnIndex(Sms.BODY));
   3344                     PushMsgInfo msgInfo = mPushMsgList.get(id);
   3345                     if (msgInfo == null || msgInfo.resend == false ||
   3346                             msgInfo.sendInProgress == true) {
   3347                         continue;
   3348                     }
   3349                     msgInfo.sendInProgress = true;
   3350                     sendMessage(msgInfo, msgBody);
   3351                 } while (c.moveToNext());
   3352             }
   3353         } finally {
   3354             if (c != null) c.close();
   3355         }
   3356 
   3357 
   3358     }
   3359 
   3360     private void failPendingMessages() {
   3361         /* Move pending messages from outbox to failed */
   3362         String where = "type = " + Sms.MESSAGE_TYPE_OUTBOX;
   3363         Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
   3364                 null);
   3365         try {
   3366             if (c != null && c.moveToFirst()) {
   3367                 do {
   3368                     long id = c.getLong(c.getColumnIndex(Sms._ID));
   3369                     String msgBody = c.getString(c.getColumnIndex(Sms.BODY));
   3370                     PushMsgInfo msgInfo = mPushMsgList.get(id);
   3371                     if (msgInfo == null || msgInfo.resend == false) {
   3372                         continue;
   3373                     }
   3374                     Sms.moveMessageToFolder(mContext, msgInfo.uri,
   3375                             Sms.MESSAGE_TYPE_FAILED, 0);
   3376                 } while (c.moveToNext());
   3377             }
   3378         } finally {
   3379             if (c != null) c.close();
   3380         }
   3381 
   3382     }
   3383 
   3384     private void removeDeletedMessages() {
   3385         /* Remove messages from virtual "deleted" folder (thread_id -1) */
   3386         mResolver.delete(Sms.CONTENT_URI,
   3387                 "thread_id = " + DELETED_THREAD_ID, null);
   3388     }
   3389 
   3390     private PhoneStateListener mPhoneListener = new PhoneStateListener() {
   3391         @Override
   3392         public void onServiceStateChanged(ServiceState serviceState) {
   3393             Log.d(TAG, "Phone service state change: " + serviceState.getState());
   3394             if (serviceState.getState() == ServiceState.STATE_IN_SERVICE) {
   3395                 resendPendingMessages();
   3396             }
   3397         }
   3398     };
   3399 
   3400     public void init() {
   3401         if (mSmsBroadcastReceiver != null) {
   3402             mSmsBroadcastReceiver.register();
   3403         }
   3404         registerPhoneServiceStateListener();
   3405         mInitialized = true;
   3406     }
   3407 
   3408     public void deinit() {
   3409         mInitialized = false;
   3410         unregisterObserver();
   3411         if (mSmsBroadcastReceiver != null) {
   3412             mSmsBroadcastReceiver.unregister();
   3413         }
   3414         unRegisterPhoneServiceStateListener();
   3415         failPendingMessages();
   3416         removeDeletedMessages();
   3417     }
   3418 
   3419     public boolean handleSmsSendIntent(Context context, Intent intent){
   3420         TYPE type = TYPE.fromOrdinal(
   3421             intent.getIntExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal()));
   3422         if(type == TYPE.MMS) {
   3423             return handleMmsSendIntent(context, intent);
   3424         } else {
   3425             if(mInitialized) {
   3426                 mSmsBroadcastReceiver.onReceive(context, intent);
   3427                 return true;
   3428             }
   3429         }
   3430         return false;
   3431     }
   3432 
   3433     public boolean handleMmsSendIntent(Context context, Intent intent){
   3434         if(D) Log.w(TAG, "handleMmsSendIntent()");
   3435         if(mMnsClient.isConnected() == false) {
   3436             // No need to handle notifications, just use default handling
   3437             if(D) Log.w(TAG, "MNS not connected - use static handling");
   3438             return false;
   3439         }
   3440         long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1);
   3441         int result = intent.getIntExtra(EXTRA_MESSAGE_SENT_RESULT, Activity.RESULT_CANCELED);
   3442         actionMmsSent(context, intent, result, getMsgListMms());
   3443         if(handle < 0) {
   3444             Log.w(TAG, "Intent received for an invalid handle");
   3445             return true;
   3446         }
   3447         if(result != Activity.RESULT_OK) {
   3448             if(mObserverRegistered) {
   3449                 Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, handle,
   3450                         getMmsFolderName(Mms.MESSAGE_BOX_OUTBOX), null, TYPE.MMS);
   3451                 sendEvent(evt);
   3452             }
   3453         } else {
   3454             int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
   3455             if(transparent != 0) {
   3456                 if(mObserverRegistered) {
   3457                     Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, handle,
   3458                             getMmsFolderName(Mms.MESSAGE_BOX_OUTBOX), null, TYPE.MMS);
   3459                     sendEvent(evt);
   3460                 }
   3461             }
   3462         }
   3463         return true;
   3464     }
   3465 
   3466 }
   3467