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