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