Home | History | Annotate | Download | only in map
      1 /*
      2 * Copyright (C) 2015 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.content.ContentProviderClient;
     18 import android.content.ContentResolver;
     19 import android.content.Context;
     20 import android.database.Cursor;
     21 import android.net.Uri;
     22 import android.os.Bundle;
     23 import android.os.Handler;
     24 import android.os.Message;
     25 import android.os.ParcelUuid;
     26 import android.os.RemoteException;
     27 import android.os.UserManager;
     28 import android.text.format.DateUtils;
     29 import android.util.Log;
     30 
     31 import com.android.bluetooth.SignedLongLong;
     32 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
     33 import com.android.bluetooth.map.BluetoothMapMasInstance;
     34 import com.android.bluetooth.mapapi.BluetoothMapContract;
     35 
     36 import java.io.IOException;
     37 import java.io.InputStream;
     38 import java.io.OutputStream;
     39 import java.text.ParseException;
     40 import java.util.Arrays;
     41 import java.util.Calendar;
     42 
     43 import javax.obex.HeaderSet;
     44 import javax.obex.Operation;
     45 import javax.obex.ResponseCodes;
     46 import javax.obex.ServerRequestHandler;
     47 
     48 
     49 public class BluetoothMapObexServer extends ServerRequestHandler {
     50 
     51     private static final String TAG = "BluetoothMapObexServer";
     52 
     53     private static final boolean D = BluetoothMapService.DEBUG;
     54     private static final boolean V = BluetoothMapService.VERBOSE;
     55 
     56     private static final int UUID_LENGTH = 16;
     57 
     58     private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS;
     59 
     60     /* OBEX header and value used to detect clients that support threadId in the message listing. */
     61     private static final int THREADED_MAIL_HEADER_ID = 0xFA;
     62     private static final long THREAD_MAIL_KEY = 0x534c5349;
     63 
     64     // 128 bit UUID for MAP
     65     private static final byte[] MAP_TARGET = new byte[] {
     66              (byte)0xBB, (byte)0x58, (byte)0x2B, (byte)0x40,
     67              (byte)0x42, (byte)0x0C, (byte)0x11, (byte)0xDB,
     68              (byte)0xB0, (byte)0xDE, (byte)0x08, (byte)0x00,
     69              (byte)0x20, (byte)0x0C, (byte)0x9A, (byte)0x66
     70              };
     71     public static final ParcelUuid MAP =
     72             ParcelUuid.fromString("00001134-0000-1000-8000-00805F9B34FB");
     73     public static final ParcelUuid MNS =
     74             ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
     75     public static final ParcelUuid MAS =
     76             ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB");
     77     /* Message types */
     78     private static final String TYPE_GET_FOLDER_LISTING              = "x-obex/folder-listing";
     79     private static final String TYPE_GET_MESSAGE_LISTING             = "x-bt/MAP-msg-listing";
     80     private static final String TYPE_GET_CONVO_LISTING               = "x-bt/MAP-convo-listing";
     81     private static final String TYPE_MESSAGE                         = "x-bt/message";
     82     private static final String TYPE_SET_MESSAGE_STATUS              = "x-bt/messageStatus";
     83     private static final String TYPE_SET_NOTIFICATION_REGISTRATION
     84             = "x-bt/MAP-NotificationRegistration";
     85     private static final String TYPE_MESSAGE_UPDATE                  = "x-bt/MAP-messageUpdate";
     86     private static final String TYPE_GET_MAS_INSTANCE_INFORMATION
     87             = "x-bt/MASInstanceInformation";
     88     private static final String TYPE_SET_OWNER_STATUS                = "x-bt/participant";
     89     private static final String TYPE_SET_NOTIFICATION_FILTER
     90             = "x-bt/MAP-notification-filter";
     91 
     92     private static final int MAS_INSTANCE_INFORMATION_LENGTH = 200;
     93 
     94     private BluetoothMapFolderElement mCurrentFolder;
     95     private BluetoothMapContentObserver mObserver = null;
     96     private Handler mCallback = null;
     97     private Context mContext;
     98     private boolean mIsAborted = false;
     99     BluetoothMapContent mOutContent;
    100     private String mBaseUriString = null;
    101     private long mAccountId = 0;
    102     private BluetoothMapAccountItem mAccount = null;
    103     private Uri mEmailFolderUri = null;
    104     private int mMasId = 0;
    105     private BluetoothMapMasInstance mMasInstance; // TODO: change to interface?
    106     // updated during connect if remote has alternative value
    107     private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
    108     private boolean mEnableSmsMms = false;
    109     private boolean mThreadIdSupport = false; // true if peer supports threadId in msg listing
    110     // Defaults message version is 1.0 but 1.1+ if feature bit is set
    111     private String mMessageVersion = BluetoothMapUtils.MAP_V10_STR;
    112     private String mAuthority;
    113     private ContentResolver mResolver;
    114     private ContentProviderClient mProviderClient = null;
    115 
    116     public BluetoothMapObexServer(Handler callback,
    117                                   Context context,
    118                                   BluetoothMapContentObserver observer,
    119                                   BluetoothMapMasInstance mas,
    120                                   BluetoothMapAccountItem account,
    121                                   boolean enableSmsMms) throws RemoteException {
    122         super();
    123         mCallback = callback;
    124         mContext = context;
    125         mObserver = observer;
    126         mEnableSmsMms = enableSmsMms;
    127         mAccount = account;
    128         mMasId = mas.getMasId();
    129         mMasInstance = mas;
    130         mRemoteFeatureMask = mMasInstance.getRemoteFeatureMask();
    131 
    132         if(account != null && account.getProviderAuthority() != null) {
    133             mAccountId = account.getAccountId();
    134             mAuthority = account.getProviderAuthority();
    135             mResolver = mContext.getContentResolver();
    136             if (D) Log.d(TAG, "BluetoothMapObexServer(): accountId=" + mAccountId);
    137             mBaseUriString = account.mBase_uri + "/";
    138             if (D) Log.d(TAG, "BluetoothMapObexServer(): baseUri=" + mBaseUriString);
    139             if (account.getType() == TYPE.EMAIL) {
    140                 mEmailFolderUri = BluetoothMapContract.buildFolderUri(mAuthority,
    141                                                                  Long.toString(mAccountId));
    142                 if (D) Log.d(TAG, "BluetoothMapObexServer(): mEmailFolderUri=" + mEmailFolderUri);
    143             }
    144             mProviderClient = acquireUnstableContentProviderOrThrow();
    145         }
    146 
    147         buildFolderStructure(); /* Build the default folder structure, and set
    148                                    mCurrentFolder to root folder */
    149         mObserver.setFolderStructure(mCurrentFolder.getRoot());
    150 
    151         mOutContent = new BluetoothMapContent(mContext, mAccount, mMasInstance);
    152 
    153     }
    154 
    155     /**
    156      *
    157      */
    158     private ContentProviderClient acquireUnstableContentProviderOrThrow() throws RemoteException {
    159         ContentProviderClient providerClient =
    160                             mResolver.acquireUnstableContentProviderClient(mAuthority);
    161         if (providerClient == null) {
    162             throw new RemoteException("Failed to acquire provider for " + mAuthority);
    163         }
    164         providerClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
    165         return providerClient;
    166     }
    167 
    168     /**
    169      * Build the default minimal folder structure, as defined in the MAP specification.
    170      */
    171     private void buildFolderStructure() throws RemoteException {
    172         mCurrentFolder = new BluetoothMapFolderElement("root", null);//This will be the root element
    173         mCurrentFolder.setHasSmsMmsContent(mEnableSmsMms);
    174         boolean hasIM = false;
    175         boolean hasEmail = false;
    176         if (mAccount != null) {
    177            if (mAccount.getType() == TYPE.IM)
    178                hasIM = true;
    179            if( mAccount.getType() == TYPE.EMAIL)
    180                hasEmail = true;
    181         }
    182         mCurrentFolder.setHasImContent(hasIM);
    183         mCurrentFolder.setHasEmailContent(hasEmail);
    184 
    185         BluetoothMapFolderElement tmpFolder;
    186         tmpFolder = mCurrentFolder.addFolder("telecom"); // root/telecom
    187         tmpFolder.setHasSmsMmsContent(mEnableSmsMms);
    188         tmpFolder.setHasImContent(hasIM);
    189         tmpFolder.setHasEmailContent(hasEmail);
    190 
    191         tmpFolder = tmpFolder.addFolder("msg");          // root/telecom/msg
    192         tmpFolder.setHasSmsMmsContent(mEnableSmsMms);
    193         tmpFolder.setHasImContent(hasIM);
    194         tmpFolder.setHasEmailContent(hasEmail);
    195 
    196         // Add the mandatory folders
    197         addBaseFolders(tmpFolder);
    198         if (mEnableSmsMms) {
    199             addSmsMmsFolders(tmpFolder);
    200         }
    201         if (hasEmail) {
    202             if (D) Log.d(TAG, "buildFolderStructure(): " + mEmailFolderUri.toString());
    203             addEmailFolders(tmpFolder);
    204         }
    205         if (hasIM) {
    206             addImFolders(tmpFolder);
    207         }
    208     }
    209 
    210     /**
    211      * Add base (Inbox/Outbox/Sent/Deleted)
    212      * @param root
    213      */
    214     private void addBaseFolders(BluetoothMapFolderElement root) {
    215         root.addFolder(BluetoothMapContract.FOLDER_NAME_INBOX);         // root/telecom/msg/inbox
    216         root.addFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX);
    217         root.addFolder(BluetoothMapContract.FOLDER_NAME_SENT);
    218         root.addFolder(BluetoothMapContract.FOLDER_NAME_DELETED);
    219     }
    220 
    221     /**
    222      * Add SMS / MMS Base folders
    223      * @param root
    224      */
    225     private void addSmsMmsFolders(BluetoothMapFolderElement root) {
    226         root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_INBOX);   // root/telecom/msg/inbox
    227         root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX);
    228         root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_SENT);
    229         root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_DELETED);
    230         root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_DRAFT);
    231     }
    232 
    233 
    234     private void addImFolders(BluetoothMapFolderElement root) throws RemoteException {
    235         // Select all parent folders
    236         root.addImFolder(BluetoothMapContract.FOLDER_NAME_INBOX,
    237                 BluetoothMapContract.FOLDER_ID_INBOX);       // root/telecom/msg/inbox
    238         root.addImFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX,
    239                 BluetoothMapContract.FOLDER_ID_OUTBOX);
    240         root.addImFolder(BluetoothMapContract.FOLDER_NAME_SENT,
    241                 BluetoothMapContract.FOLDER_ID_SENT);
    242         root.addImFolder(BluetoothMapContract.FOLDER_NAME_DELETED,
    243                 BluetoothMapContract.FOLDER_ID_DELETED);
    244         root.addImFolder(BluetoothMapContract.FOLDER_NAME_DRAFT,
    245                 BluetoothMapContract.FOLDER_ID_DRAFT);
    246     }
    247 
    248     /**
    249      * Recursively adds folders based on the folders in the email content provider.
    250      *       Add a content observer? - to refresh the folder list if any change occurs.
    251      *       Consider simply deleting the entire table, and then rebuild using
    252      *       buildFolderStructure()
    253      *       WARNING: there is no way to notify the client about these changes - hence
    254      *       we need to either keep the folder structure constant, disconnect or fail anything
    255      *       referring to currentFolder.
    256      *       It is unclear what to set as current folder to be able to go one level up...
    257      *       The best solution would be to keep the folder structure constant during a connection.
    258      * @param folder the parent folder to which subFolders needs to be added. The
    259      *        folder.getFolderId() will be used to query sub-folders.
    260      *        Use a parentFolder with id -1 to get all folders from root.
    261      */
    262     private void addEmailFolders(BluetoothMapFolderElement parentFolder) throws RemoteException {
    263         // Select all parent folders
    264         BluetoothMapFolderElement newFolder;
    265 
    266         String where = BluetoothMapContract.FolderColumns.PARENT_FOLDER_ID +
    267                         " = " + parentFolder.getFolderId();
    268         Cursor c = mProviderClient.query(mEmailFolderUri,
    269                         BluetoothMapContract.BT_FOLDER_PROJECTION, where, null, null);
    270         try {
    271             if (c != null) {
    272                 c.moveToPosition(-1);
    273                 while (c.moveToNext()) {
    274                     String name = c.getString(c.getColumnIndex(
    275                             BluetoothMapContract.FolderColumns.NAME));
    276                     long id = c.getLong(c.getColumnIndex(BluetoothMapContract.FolderColumns._ID));
    277                     newFolder = parentFolder.addEmailFolder(name, id);
    278                     addEmailFolders(newFolder); // Use recursion to add any sub folders
    279                 }
    280 
    281             } else {
    282                 if (D) Log.d(TAG, "addEmailFolders(): no elements found");
    283             }
    284         } finally {
    285             if (c != null) c.close();
    286         }
    287     }
    288 
    289     @Override
    290     public boolean isSrmSupported() {
    291         // TODO: Update based on the transport used
    292         return true;
    293     }
    294 
    295     public int getRemoteFeatureMask() {
    296         return mRemoteFeatureMask;
    297     }
    298 
    299     public void setRemoteFeatureMask(int mRemoteFeatureMask) {
    300         if(D) Log.d(TAG, "setRemoteFeatureMask() " + Integer.toHexString(mRemoteFeatureMask));
    301         this.mRemoteFeatureMask = mRemoteFeatureMask;
    302         this.mOutContent.setRemoteFeatureMask(mRemoteFeatureMask);
    303     }
    304 
    305     @Override
    306     public int onConnect(final HeaderSet request, HeaderSet reply) {
    307         if (D) Log.d(TAG, "onConnect():");
    308         if (V) logHeader(request);
    309         mThreadIdSupport = false; // Always assume not supported at new connect.
    310         mMessageVersion = BluetoothMapUtils.MAP_V10_STR;//always assume version 1.0 to start with
    311         notifyUpdateWakeLock();
    312         Long threadedMailKey = null;
    313         try {
    314             byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET);
    315             threadedMailKey = (Long)request.getHeader(THREADED_MAIL_HEADER_ID);
    316             if (uuid == null) {
    317                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    318             }
    319             if (D) Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid));
    320 
    321             if (uuid.length != UUID_LENGTH) {
    322                 Log.w(TAG, "Wrong UUID length");
    323                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    324             }
    325             for (int i = 0; i < UUID_LENGTH; i++) {
    326                 if (uuid[i] != MAP_TARGET[i]) {
    327                     Log.w(TAG, "Wrong UUID");
    328                     return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    329                 }
    330             }
    331             reply.setHeader(HeaderSet.WHO, uuid);
    332         } catch (IOException e) {
    333             Log.e(TAG,"Exception during onConnect:", e);
    334             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    335         }
    336 
    337         try {
    338             byte[] remote = (byte[])request.getHeader(HeaderSet.WHO);
    339             if (remote != null) {
    340                 if (D) Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote));
    341                 reply.setHeader(HeaderSet.TARGET, remote);
    342             }
    343             if(threadedMailKey != null && threadedMailKey.longValue() == THREAD_MAIL_KEY)
    344             {
    345                 /* If the client provides the correct key we enable threaded e-mail support
    346                  * and reply to the client that we support the requested feature.
    347                  * This is currently an Android only feature. */
    348                 mThreadIdSupport = true;
    349                 reply.setHeader(THREADED_MAIL_HEADER_ID, THREAD_MAIL_KEY);
    350             }
    351         } catch (IOException e) {
    352             Log.e(TAG,"Exception during onConnect:", e);
    353             mThreadIdSupport = false;
    354             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    355         }
    356 
    357         if((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)
    358                 == BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) {
    359             mThreadIdSupport = true;
    360         }
    361 
    362         if((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_FORMAT_V11_BIT)
    363                 == BluetoothMapUtils.MAP_FEATURE_MESSAGE_FORMAT_V11_BIT){
    364             mMessageVersion = BluetoothMapUtils.MAP_V11_STR;
    365         }
    366 
    367         if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " +
    368                 "MSG_SESSION_ESTABLISHED msg.");
    369 
    370         if(mCallback != null) {
    371             Message msg = Message.obtain(mCallback);
    372             msg.what = BluetoothMapService.MSG_SESSION_ESTABLISHED;
    373             msg.sendToTarget();
    374         }
    375 
    376         return ResponseCodes.OBEX_HTTP_OK;
    377     }
    378 
    379     @Override
    380     public void onDisconnect(final HeaderSet req, final HeaderSet resp) {
    381         if (D) Log.d(TAG, "onDisconnect(): enter");
    382         if (V) logHeader(req);
    383         notifyUpdateWakeLock();
    384         resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
    385         if (mCallback != null) {
    386             Message msg = Message.obtain(mCallback);
    387             msg.what = BluetoothMapService.MSG_SESSION_DISCONNECTED;
    388             msg.sendToTarget();
    389             if (V) Log.v(TAG, "onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out.");
    390         }
    391     }
    392 
    393     @Override
    394     public int onAbort(HeaderSet request, HeaderSet reply) {
    395         if (D) Log.d(TAG, "onAbort(): enter.");
    396         notifyUpdateWakeLock();
    397         mIsAborted = true;
    398         return ResponseCodes.OBEX_HTTP_OK;
    399     }
    400 
    401     private boolean isUserUnlocked() {
    402         UserManager manager = UserManager.get(mContext);
    403         return (manager == null || manager.isUserUnlocked());
    404     }
    405 
    406     @Override
    407     public int onPut(final Operation op) {
    408         if (D) Log.d(TAG, "onPut(): enter");
    409         mIsAborted = false;
    410         notifyUpdateWakeLock();
    411         HeaderSet request = null;
    412         String type, name;
    413         byte[] appParamRaw;
    414         BluetoothMapAppParams appParams = null;
    415 
    416         try {
    417             request = op.getReceivedHeader();
    418             if (V) logHeader(request);
    419             type = (String)request.getHeader(HeaderSet.TYPE);
    420             name = (String)request.getHeader(HeaderSet.NAME);
    421             appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
    422             if(appParamRaw != null)
    423                 appParams = new BluetoothMapAppParams(appParamRaw);
    424             if(D) Log.d(TAG,"type = " + type + ", name = " + name);
    425             if (type.equals(TYPE_MESSAGE_UPDATE)) {
    426                 if(V) {
    427                     Log.d(TAG,"TYPE_MESSAGE_UPDATE:");
    428                 }
    429                 return updateInbox();
    430             } else if (type.equals(TYPE_SET_NOTIFICATION_REGISTRATION)) {
    431                 if(V) {
    432                     Log.d(TAG,"TYPE_SET_NOTIFICATION_REGISTRATION: NotificationStatus: "
    433                             + appParams.getNotificationStatus());
    434                 }
    435                 return mObserver.setNotificationRegistration(appParams.getNotificationStatus());
    436             } else if (type.equals(TYPE_SET_NOTIFICATION_FILTER)) {
    437                 if(V) {
    438                     Log.d(TAG,"TYPE_SET_NOTIFICATION_FILTER: NotificationFilter: "
    439                             + appParams.getNotificationFilter());
    440                 }
    441                 if (!isUserUnlocked()) {
    442                     Log.e(TAG, "Storage locked, " + type + " failed");
    443                     return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    444                 }
    445                 mObserver.setNotificationFilter(appParams.getNotificationFilter());
    446                 return ResponseCodes.OBEX_HTTP_OK;
    447             } else if (type.equals(TYPE_SET_MESSAGE_STATUS)) {
    448                 if(V) {
    449                     Log.d(TAG,"TYPE_SET_MESSAGE_STATUS: " +
    450                               "StatusIndicator: " + appParams.getStatusIndicator()
    451                             + ", StatusValue: " + appParams.getStatusValue()
    452                             + ", ExtentedData: " + "" ); // TODO:   appParams.getExtendedImData());
    453                 }
    454                 if (!isUserUnlocked()) {
    455                     Log.e(TAG, "Storage locked, " + type + " failed");
    456                     return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    457                 }
    458                 return setMessageStatus(name, appParams);
    459             } else if (type.equals(TYPE_MESSAGE)) {
    460                 if(V) {
    461                     Log.d(TAG,"TYPE_MESSAGE: Transparet: " + appParams.getTransparent()
    462                             + ", retry: " + appParams.getRetry()
    463                             + ", charset: " + appParams.getCharset());
    464                 }
    465                 if (!isUserUnlocked()) {
    466                     Log.e(TAG, "Storage locked, " + type + " failed");
    467                     return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    468                 }
    469                 return pushMessage(op, name, appParams, mMessageVersion);
    470             } else if (type.equals(TYPE_SET_OWNER_STATUS)) {
    471                 if(V) {
    472                     Log.d(TAG,"TYPE_SET_OWNER_STATUS:" +
    473                           " PresenceAvailability " + appParams.getPresenceAvailability() +
    474                           ", PresenceStatus: " + appParams.getPresenceStatus() +
    475                           ", LastActivity: " + appParams.getLastActivityString() +
    476                           ", ChatStatus: " + appParams.getChatState() +
    477                           ", ChatStatusConvoId: " + appParams.getChatStateConvoIdString());
    478                 }
    479                 return setOwnerStatus(name, appParams);
    480             }
    481 
    482         } catch (RemoteException e){
    483             //reload the providerClient and return error
    484             try {
    485                 mProviderClient = acquireUnstableContentProviderOrThrow();
    486             }catch (RemoteException e2){
    487                 //should not happen
    488             }
    489             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    490         }catch (Exception e) {
    491 
    492             if(D) {
    493                 Log.e(TAG, "Exception occured while handling request",e);
    494             } else {
    495                 Log.e(TAG, "Exception occured while handling request");
    496             }
    497             if(mIsAborted) {
    498                 return ResponseCodes.OBEX_HTTP_OK;
    499             } else {
    500                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    501             }
    502         }
    503         return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    504     }
    505 
    506     private int updateInbox() throws RemoteException{
    507         if (mAccount != null) {
    508             BluetoothMapFolderElement inboxFolder = mCurrentFolder.getFolderByName(
    509                     BluetoothMapContract.FOLDER_NAME_INBOX);
    510             if (inboxFolder != null) {
    511                 long accountId = mAccountId;
    512                 if (D) Log.d(TAG,"updateInbox inbox=" + inboxFolder.getName() + "id="
    513                         + inboxFolder.getFolderId());
    514 
    515                 final Bundle extras = new Bundle(2);
    516                 if (accountId != -1) {
    517                     if (D) Log.d(TAG,"updateInbox accountId=" + accountId);
    518                     extras.putLong(BluetoothMapContract.EXTRA_UPDATE_FOLDER_ID,
    519                             inboxFolder.getFolderId());
    520                     extras.putLong(BluetoothMapContract.EXTRA_UPDATE_ACCOUNT_ID, accountId);
    521                 } else {
    522                     // Only error code allowed on an UpdateInbox is OBEX_HTTP_NOT_IMPLEMENTED,
    523                     // i.e. if e.g. update not allowed on the mailbox
    524                     if (D) Log.d(TAG,"updateInbox accountId=0 -> OBEX_HTTP_NOT_IMPLEMENTED");
    525                     return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
    526                 }
    527 
    528                 Uri emailUri = Uri.parse(mBaseUriString);
    529                 if (D) Log.d(TAG,"updateInbox in: " + emailUri.toString());
    530                 try {
    531                     if (D) Log.d(TAG,"updateInbox call()...");
    532                     Bundle myBundle = mProviderClient.call(
    533                             BluetoothMapContract.METHOD_UPDATE_FOLDER, null, extras);
    534                     if (myBundle != null)
    535                         return ResponseCodes.OBEX_HTTP_OK;
    536                     else {
    537                         if (D) Log.d(TAG,"updateInbox call failed");
    538                         return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
    539                     }
    540                 } catch (RemoteException e){
    541                     mProviderClient = acquireUnstableContentProviderOrThrow();
    542                     return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    543                 }catch (NullPointerException e) {
    544                     if(D) Log.e(TAG, "UpdateInbox - if uri or method is null", e);
    545                     return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    546 
    547                 } catch (IllegalArgumentException e) {
    548                     if(D) Log.e(TAG, "UpdateInbox - if uri is not known", e);
    549                     return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    550                 }
    551             }
    552         }
    553 
    554         return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
    555     }
    556 
    557      private BluetoothMapFolderElement getFolderElementFromName(String folderName) {
    558         BluetoothMapFolderElement folderElement = null;
    559 
    560         if(folderName == null || folderName.trim().isEmpty() ) {
    561             folderElement = mCurrentFolder;
    562             if(D) Log.d(TAG, "no folder name supplied, setting folder to current: "
    563                              + folderElement.getName());
    564         } else {
    565             folderElement = mCurrentFolder.getSubFolder(folderName);
    566             if (folderElement != null) {
    567                 if(D) Log.d(TAG, "Folder name: " + folderName +
    568                                  " resulted in this element: " + folderElement.getName());
    569             }
    570         }
    571         return folderElement;
    572     }
    573 
    574     private int pushMessage(final Operation op, String folderName,
    575             BluetoothMapAppParams appParams, String messageVersion) {
    576         if(appParams.getCharset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
    577             if(D) Log.d(TAG, "pushMessage: Missing charset - unable to decode message content. " +
    578                     "appParams.getCharset() = " + appParams.getCharset());
    579             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    580         }
    581         InputStream bMsgStream = null;
    582         try {
    583             BluetoothMapFolderElement folderElement = getFolderElementFromName(folderName);
    584             if(folderElement == null) {
    585                 Log.w(TAG,"pushMessage: folderElement == null - sending OBEX_HTTP_PRECON_FAILED");
    586                 return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    587             } else {
    588                 folderName = folderElement.getName();
    589             }
    590             if (!folderName.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX) &&
    591                     !folderName.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT)) {
    592                 if(D) Log.d(TAG, "pushMessage: Is only allowed to outbox and draft. " +
    593                         "folderName=" + folderName);
    594                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    595             }
    596 
    597             /*  - Read out the message
    598              *  - Decode into a bMessage
    599              *  - send it.
    600              */
    601             BluetoothMapbMessage message;
    602             bMsgStream = op.openInputStream();
    603             // Decode the messageBody
    604             message = BluetoothMapbMessage.parse(bMsgStream, appParams.getCharset());
    605             message.setVersionString(messageVersion);
    606             // Send message
    607             if (mObserver == null || message == null) {
    608                 // Should not happen except at shutdown.
    609                 if(D) Log.w(TAG, "mObserver or parsed message not available" );
    610                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    611             }
    612 
    613             if ((message.getType().equals(TYPE.EMAIL) && (folderElement.getFolderId() == -1))
    614                     || ((message.getType().equals(TYPE.SMS_GSM) ||
    615                          message.getType().equals(TYPE.SMS_CDMA) ||
    616                          message.getType().equals(TYPE.MMS))
    617                          && !folderElement.hasSmsMmsContent()) ) {
    618                 if(D) Log.w(TAG, "Wrong message type recieved" );
    619                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    620             }
    621 
    622             long handle = mObserver.pushMessage(message, folderElement, appParams, mBaseUriString);
    623             if (D) Log.d(TAG, "pushMessage handle: " + handle);
    624             if (handle < 0) {
    625                 if(D) Log.w(TAG, "Message  handle not created" );
    626                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
    627             }
    628             HeaderSet replyHeaders = new HeaderSet();
    629             String handleStr = BluetoothMapUtils.getMapHandle(handle, message.getType());
    630             if (D) Log.d(TAG, "handleStr: " + handleStr + " message.getType(): "
    631                                + message.getType());
    632             replyHeaders.setHeader(HeaderSet.NAME, handleStr);
    633             op.sendHeaders(replyHeaders);
    634 
    635         } catch (RemoteException e) {
    636             //reload the providerClient and return error
    637             try {
    638                 mProviderClient = acquireUnstableContentProviderOrThrow();
    639             }catch (RemoteException e2){
    640                 //should not happen
    641                 if(D) Log.w(TAG, "acquireUnstableContentProviderOrThrow FAILED");
    642             }
    643             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    644         } catch (IllegalArgumentException e) {
    645             if (D) Log.e(TAG, "Wrongly formatted bMessage received", e);
    646             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    647         } catch (IOException e) {
    648             if (D) Log.e(TAG, "Exception occured: ", e);
    649             if(mIsAborted == true) {
    650                 if(D) Log.d(TAG, "PushMessage Operation Aborted");
    651                 return ResponseCodes.OBEX_HTTP_OK;
    652             } else {
    653                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    654             }
    655         } catch (Exception e) {
    656             if (D) Log.e(TAG, "Exception:", e);
    657             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    658         } finally {
    659             if(bMsgStream != null) {
    660                 try {
    661                     bMsgStream.close();
    662                 } catch (IOException e) {}
    663             }
    664         }
    665         return ResponseCodes.OBEX_HTTP_OK;
    666     }
    667 
    668     private int setMessageStatus(String msgHandle, BluetoothMapAppParams appParams) {
    669         int indicator = appParams.getStatusIndicator();
    670         int value = appParams.getStatusValue();
    671         String extendedData = ""; // TODO: appParams.getExtendedImData();
    672 
    673         long handle;
    674         BluetoothMapUtils.TYPE msgType;
    675 
    676         if (msgHandle == null) {
    677             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    678         } else if ((indicator == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
    679                    value == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) &&
    680                    extendedData == null) {
    681             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    682         }
    683         if (mObserver == null) {
    684             if(D) Log.e(TAG, "Error: no mObserver!");
    685             return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
    686         }
    687 
    688         try {
    689             handle = BluetoothMapUtils.getCpHandle(msgHandle);
    690             msgType = BluetoothMapUtils.getMsgTypeFromHandle(msgHandle);
    691             if(D) Log.d(TAG,"setMessageStatus. Handle:" + handle+", MsgType: "+ msgType);
    692         } catch (NumberFormatException e) {
    693             Log.w(TAG, "Wrongly formatted message handle: " + msgHandle);
    694             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    695         } catch (IllegalArgumentException e) {
    696             Log.w(TAG, "Message type not found in handle string: " + msgHandle);
    697             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    698         }
    699 
    700         if( indicator == BluetoothMapAppParams.STATUS_INDICATOR_DELETED) {
    701             if (!mObserver.setMessageStatusDeleted(handle, msgType, mCurrentFolder,
    702                     mBaseUriString, value)) {
    703                 if(D) Log.w(TAG,"setMessageStatusDeleted failed");
    704                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    705             }
    706         } else if( indicator == BluetoothMapAppParams.STATUS_INDICATOR_READ) {
    707             try {
    708                 if (!mObserver.setMessageStatusRead(handle, msgType, mBaseUriString, value)) {
    709                     if(D) Log.w(TAG,"not able to update the message");
    710                     return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    711                 }
    712             } catch (RemoteException e) {
    713                 if(D) Log.w(TAG,"Error in setMessageStatusRead()", e);
    714                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    715             }
    716         }
    717         if (extendedData != null) {
    718 
    719         }
    720 
    721         return ResponseCodes.OBEX_HTTP_OK;
    722     }
    723 
    724     private int setOwnerStatus(String msgHandle, BluetoothMapAppParams appParams)
    725             throws RemoteException{
    726         // This does only work for IM
    727         if (mAccount != null && mAccount.getType() == BluetoothMapUtils.TYPE.IM) {
    728             final Bundle extras = new Bundle(5);
    729 
    730             int presenceState = appParams.getPresenceAvailability();
    731             String presenceStatus = appParams.getPresenceStatus();
    732             long lastActivity = appParams.getLastActivity();
    733             int chatState = appParams.getChatState();
    734             String chatStatusConvoId = appParams.getChatStateConvoIdString();
    735 
    736             if(presenceState == BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
    737                presenceStatus == null &&
    738                lastActivity == BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
    739                chatState == BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
    740                chatStatusConvoId == null) {
    741                 return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    742             }
    743 
    744             if(presenceState != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
    745                 extras.putInt(BluetoothMapContract.EXTRA_PRESENCE_STATE, presenceState);
    746             }
    747             if (presenceStatus != null){
    748                 extras.putString(BluetoothMapContract.EXTRA_PRESENCE_STATUS, presenceStatus);
    749             }
    750             if (lastActivity != BluetoothMapAppParams.INVALID_VALUE_PARAMETER){
    751                 extras.putLong(BluetoothMapContract.EXTRA_LAST_ACTIVE, lastActivity);
    752             }
    753             if (chatState != BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
    754                 chatStatusConvoId != null){
    755                 extras.putInt(BluetoothMapContract.EXTRA_CHAT_STATE, chatState);
    756                 extras.putString(BluetoothMapContract.EXTRA_CONVERSATION_ID, chatStatusConvoId);
    757             }
    758 
    759             Uri uri = Uri.parse(mBaseUriString);
    760             if (D) Log.d(TAG,"setOwnerStatus in: " + uri.toString());
    761             try {
    762                 if (D) Log.d(TAG,"setOwnerStatus call()...");
    763                 Bundle myBundle = mProviderClient.call(
    764                         BluetoothMapContract.METHOD_SET_OWNER_STATUS, null, extras);
    765                 if (myBundle != null)
    766                     return ResponseCodes.OBEX_HTTP_OK;
    767                 else {
    768                     if (D) Log.d(TAG,"setOwnerStatus call failed");
    769                     return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
    770                 }
    771             } catch (RemoteException e){
    772                 mProviderClient = acquireUnstableContentProviderOrThrow();
    773                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    774             } catch (NullPointerException e) {
    775                 if(D) Log.e(TAG, "setOwnerStatus - if uri or method is null", e);
    776                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    777             } catch (IllegalArgumentException e) {
    778                 if(D) Log.e(TAG, "setOwnerStatus - if uri is not known", e);
    779                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    780             }
    781         }
    782         return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    783     }
    784 
    785     @Override
    786     public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup,
    787             final boolean create) {
    788         String folderName;
    789         BluetoothMapFolderElement folder;
    790         notifyUpdateWakeLock();
    791         try {
    792             folderName = (String)request.getHeader(HeaderSet.NAME);
    793         } catch (Exception e) {
    794             if(D) {
    795                 Log.e(TAG, "request headers error" , e);
    796             } else {
    797                 Log.e(TAG, "request headers error");
    798             }
    799             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    800         }
    801 
    802         if (V) logHeader(request);
    803         if (D) Log.d(TAG, "onSetPath name is " + folderName +
    804                           " backup: " + backup +
    805                           " create: " + create);
    806 
    807         if(backup == true){
    808             if(mCurrentFolder.getParent() != null)
    809                 mCurrentFolder = mCurrentFolder.getParent();
    810             else
    811                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    812         }
    813 
    814         if (folderName == null || folderName.trim().isEmpty()) {
    815             if(backup == false)
    816                 mCurrentFolder = mCurrentFolder.getRoot();
    817         }
    818         else {
    819             folder = mCurrentFolder.getSubFolder(folderName);
    820             if(folder != null)
    821                 mCurrentFolder = folder;
    822             else
    823                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    824         }
    825         if (V) Log.d(TAG, "Current Folder: " + mCurrentFolder.getName());
    826         return ResponseCodes.OBEX_HTTP_OK;
    827     }
    828 
    829     @Override
    830     public void onClose() {
    831         if (mCallback != null) {
    832             Message msg = Message.obtain(mCallback);
    833             msg.what = BluetoothMapService.MSG_SERVERSESSION_CLOSE;
    834             msg.arg1 = mMasId;
    835             msg.sendToTarget();
    836             if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out.");
    837 
    838         }
    839         if(mProviderClient != null){
    840             mProviderClient.release();
    841             mProviderClient = null;
    842         }
    843 
    844     }
    845 
    846     @Override
    847     public int onGet(Operation op) {
    848         notifyUpdateWakeLock();
    849         mIsAborted = false;
    850         HeaderSet request;
    851         String type;
    852         String name;
    853         byte[] appParamRaw = null;
    854         BluetoothMapAppParams appParams = null;
    855         try {
    856             request = op.getReceivedHeader();
    857             type = (String)request.getHeader(HeaderSet.TYPE);
    858 
    859             appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
    860             if(appParamRaw != null)
    861                 appParams = new BluetoothMapAppParams(appParamRaw);
    862 
    863             if (V) logHeader(request);
    864             if (D) Log.d(TAG, "OnGet type is " + type );
    865 
    866             if (type == null) {
    867                 if (V) Log.d(TAG, "type is null?" + type);
    868                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    869             }
    870 
    871             if (type.equals(TYPE_GET_FOLDER_LISTING)) {
    872                 if (V && appParams != null) {
    873                     Log.d(TAG,"TYPE_GET_FOLDER_LISTING: MaxListCount = "
    874                             + appParams.getMaxListCount()
    875                             + ", ListStartOffset = " + appParams.getStartOffset());
    876                 }
    877                 // Block until all packets have been send.
    878                 return sendFolderListingRsp(op, appParams);
    879             } else if (type.equals(TYPE_GET_MESSAGE_LISTING)){
    880                 name = (String)request.getHeader(HeaderSet.NAME);
    881                 if (V && appParams != null) {
    882                     Log.d(TAG,"TYPE_GET_MESSAGE_LISTING: folder name is: " + name +
    883                             ", MaxListCount = " + appParams.getMaxListCount() +
    884                             ", ListStartOffset = " + appParams.getStartOffset());
    885                     Log.d(TAG,"SubjectLength = " + appParams.getSubjectLength() +
    886                             ", ParameterMask = " + appParams.getParameterMask());
    887                     Log.d(TAG,"FilterMessageType = " + appParams.getFilterMessageType() );
    888                     Log.d(TAG,"FilterPeriodBegin = " + appParams.getFilterPeriodBeginString() +
    889                             ", FilterPeriodEnd = " + appParams.getFilterPeriodEndString() +
    890                             ", FilterReadStatus = " + appParams.getFilterReadStatus());
    891                     Log.d(TAG,"FilterRecipient = " + appParams.getFilterRecipient() +
    892                             ", FilterOriginator = " + appParams.getFilterOriginator());
    893                     Log.d(TAG,"FilterPriority = " + appParams.getFilterPriority());
    894                     long tmpLong = appParams.getFilterMsgHandle();
    895                     Log.d(TAG,"FilterMsgHandle = " + (
    896                             (tmpLong == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) ? "" :
    897                                 Long.toHexString(tmpLong)));
    898                     SignedLongLong tmpLongLong = appParams.getFilterConvoId();
    899                     Log.d(TAG,"FilterConvoId = " + ((tmpLongLong == null) ? "" :
    900                         Long.toHexString(tmpLongLong.getLeastSignificantBits()) ) );
    901                 }
    902                 if (!isUserUnlocked()) {
    903                     Log.e(TAG, "Storage locked, " +  type + " failed");
    904                     return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    905                 }
    906                 // Block until all packets have been send.
    907                 return sendMessageListingRsp(op, appParams, name);
    908 
    909             } else if (type.equals(TYPE_GET_CONVO_LISTING)){
    910                 name = (String)request.getHeader(HeaderSet.NAME);
    911                 if (V && appParams != null) {
    912                     Log.d(TAG,"TYPE_GET_CONVO_LISTING: name is" + name +
    913                             ", MaxListCount = " + appParams.getMaxListCount() +
    914                             ", ListStartOffset = " + appParams.getStartOffset());
    915                     Log.d(TAG,"FilterLastActivityBegin = "+appParams.getFilterLastActivityBegin());
    916                     Log.d(TAG,"FilterLastActivityEnd = " + appParams.getFilterLastActivityEnd());
    917                     Log.d(TAG,"FilterReadStatus = " + appParams.getFilterReadStatus());
    918                     Log.d(TAG,"FilterRecipient = " + appParams.getFilterRecipient());
    919                 }
    920                 if (!isUserUnlocked()) {
    921                     Log.e(TAG, "Storage locked, " + type + " failed");
    922                     return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    923                 }
    924                 // Block until all packets have been send.
    925                 return sendConvoListingRsp(op, appParams,name);
    926             } else if (type.equals(TYPE_GET_MAS_INSTANCE_INFORMATION)) {
    927                 if(V && appParams != null) {
    928                     Log.d(TAG,"TYPE_MESSAGE (GET): MASInstandeId = "
    929                             + appParams.getMasInstanceId());
    930                 }
    931                 // Block until all packets have been send.
    932                 return sendMASInstanceInformationRsp(op, appParams);
    933             } else if (type.equals(TYPE_MESSAGE)){
    934                 name = (String)request.getHeader(HeaderSet.NAME);
    935                 if(V && appParams != null) {
    936                     Log.d(TAG,"TYPE_MESSAGE (GET): name is" + name +
    937                             ", Attachment = " + appParams.getAttachment() +
    938                             ", Charset = " + appParams.getCharset() +
    939                             ", FractionRequest = " + appParams.getFractionRequest());
    940                 }
    941                 if (!isUserUnlocked()) {
    942                     Log.e(TAG, "Storage locked, " + type + " failed");
    943                     return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    944                 }
    945                 // Block until all packets have been send.
    946                 return sendGetMessageRsp(op, name, appParams, mMessageVersion);
    947             } else {
    948                 Log.w(TAG, "unknown type request: " + type);
    949                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    950             }
    951 
    952         } catch (IllegalArgumentException e) {
    953             Log.e(TAG, "Exception:", e);
    954             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    955         } catch (ParseException e) {
    956             Log.e(TAG, "Exception:", e);
    957             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    958         } catch (Exception e) {
    959             if(D) {
    960                 Log.e(TAG, "Exception occured while handling request",e);
    961             } else {
    962                 Log.e(TAG, "Exception occured while handling request");
    963             }
    964             if(mIsAborted == true) {
    965                 if(D) Log.d(TAG, "onGet Operation Aborted");
    966                 return ResponseCodes.OBEX_HTTP_OK;
    967             } else {
    968                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    969             }
    970         }
    971     }
    972 
    973     /**
    974      * Generate and send the message listing response based on an application
    975      * parameter header. This function call will block until complete or aborted
    976      * by the peer. Fragmentation of packets larger than the obex packet size
    977      * will be handled by this function.
    978      *
    979      * @param op
    980      *            The OBEX operation.
    981      * @param appParams
    982      *            The application parameter header
    983      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
    984      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
    985      */
    986     private int sendMessageListingRsp(Operation op,
    987                                       BluetoothMapAppParams appParams,
    988                                       String folderName){
    989         OutputStream outStream = null;
    990         byte[] outBytes = null;
    991         int maxChunkSize, bytesToWrite, bytesWritten = 0, listSize;
    992         boolean hasUnread = false;
    993         HeaderSet replyHeaders = new HeaderSet();
    994         BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
    995         BluetoothMapMessageListing outList;
    996         if(appParams == null){
    997             appParams = new BluetoothMapAppParams();
    998             appParams.setMaxListCount(1024);
    999             appParams.setStartOffset(0);
   1000         }
   1001 
   1002         /* MAP Spec 1.3 introduces the following
   1003          * Messagehandle filtering:
   1004          * msgListing (messageHandle=X) -> other allowed filters: parametereMask, subjectMaxLength
   1005          * ConversationID filtering:
   1006          * msgListing (convoId empty) -> should work as normal msgListing in valid folders
   1007          * msgListing (convoId=0, no other filters) -> should return all messages in all folders
   1008          * msgListing (convoId=N, other filters) -> should return all messages in conversationID=N
   1009          *                                          according to filters requested
   1010          */
   1011         BluetoothMapFolderElement folderToList = null;
   1012         if (appParams.getFilterMsgHandle() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER
   1013             || appParams.getFilterConvoId() != null) {
   1014             // If messageHandle or convoId filtering ignore folder
   1015             Log.v(TAG,"sendMessageListingRsp: ignore folder ");
   1016             folderToList = mCurrentFolder.getRoot();
   1017             folderToList.setIngore(true);
   1018         } else {
   1019             folderToList = getFolderElementFromName(folderName);
   1020             if(folderToList == null) {
   1021                 Log.w(TAG,"sendMessageListingRsp: folderToList == "+
   1022                         "null-sending OBEX_HTTP_BAD_REQUEST");
   1023                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
   1024             }
   1025             Log.v(TAG,"sendMessageListingRsp: has sms " + folderToList.hasSmsMmsContent() +
   1026                     ", has email " + folderToList.hasEmailContent() +
   1027                     ", has IM " + folderToList.hasImContent() );
   1028         }
   1029 
   1030         try {
   1031             // Open the OBEX body stream
   1032             outStream = op.openOutputStream();
   1033 
   1034             if(appParams.getMaxListCount() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
   1035                 appParams.setMaxListCount(1024);
   1036 
   1037             if(appParams.getStartOffset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
   1038                 appParams.setStartOffset(0);
   1039 
   1040             // Check to see if we only need to send the size - hence no need to encode.
   1041             if(appParams.getMaxListCount() != 0) {
   1042                 outList = mOutContent.msgListing(folderToList, appParams);
   1043                 // Generate the byte stream
   1044                 outAppParams.setMessageListingSize(outList.getCount());
   1045                 String version;
   1046                 if(0 < (mRemoteFeatureMask &
   1047                         BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)) {
   1048                     version = BluetoothMapUtils.MAP_V11_STR;
   1049                 } else {
   1050                     version = BluetoothMapUtils.MAP_V10_STR;
   1051                 }
   1052                 /* This will only set the version, the bit must also be checked before adding any
   1053                  * 1.1 bits to the listing. */
   1054                 outBytes = outList.encode(mThreadIdSupport, version);
   1055                 hasUnread = outList.hasUnread();
   1056             } else {
   1057                 listSize = mOutContent.msgListingSize(folderToList, appParams);
   1058                 hasUnread = mOutContent.msgListingHasUnread(folderToList, appParams);
   1059                 outAppParams.setMessageListingSize(listSize);
   1060                 op.noBodyHeader();
   1061             }
   1062             folderToList.setIngore(false);
   1063             // Build the application parameter header
   1064             // let the peer know if there are unread messages in the list
   1065             if(hasUnread) {
   1066                 outAppParams.setNewMessage(1);
   1067             }else{
   1068                 outAppParams.setNewMessage(0);
   1069             }
   1070             if ((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_DATABASE_INDENTIFIER_BIT)
   1071                     == BluetoothMapUtils.MAP_FEATURE_DATABASE_INDENTIFIER_BIT ) {
   1072                 outAppParams.setDatabaseIdentifier(0, mMasInstance.getDbIdentifier());
   1073             }
   1074             if((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT)
   1075                     == BluetoothMapUtils.MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT) {
   1076                 // Force update of version counter if needed
   1077                 mObserver.refreshFolderVersionCounter();
   1078                 outAppParams.setFolderVerCounter(mMasInstance.getFolderVersionCounter(), 0);
   1079             }
   1080             outAppParams.setMseTime(Calendar.getInstance().getTime().getTime());
   1081             replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
   1082             op.sendHeaders(replyHeaders);
   1083 
   1084         } catch (IOException e) {
   1085             Log.w(TAG,"sendMessageListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
   1086             if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
   1087             if(mIsAborted == true) {
   1088                 if(D) Log.d(TAG, "sendMessageListingRsp Operation Aborted");
   1089                 return ResponseCodes.OBEX_HTTP_OK;
   1090             } else {
   1091                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
   1092             }
   1093         } catch (IllegalArgumentException e) {
   1094             Log.w(TAG,"sendMessageListingRsp: IllegalArgumentException"+
   1095                                             " - sending OBEX_HTTP_BAD_REQUEST", e);
   1096             if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
   1097             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
   1098         }
   1099 
   1100         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
   1101         if(outBytes != null) {
   1102             try {
   1103                 while (bytesWritten < outBytes.length && mIsAborted == false) {
   1104                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
   1105                     outStream.write(outBytes, bytesWritten, bytesToWrite);
   1106                     bytesWritten += bytesToWrite;
   1107                 }
   1108             } catch (IOException e) {
   1109                 if(D) Log.w(TAG,e);
   1110                 // We were probably aborted or disconnected
   1111             } finally {
   1112                 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
   1113             }
   1114             if(bytesWritten != outBytes.length && !mIsAborted) {
   1115                 Log.w(TAG,"sendMessageListingRsp: bytesWritten != outBytes.length" +
   1116                         " - sending OBEX_HTTP_BAD_REQUEST");
   1117                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
   1118             }
   1119         } else {
   1120             if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
   1121         }
   1122         return ResponseCodes.OBEX_HTTP_OK;
   1123     }
   1124 
   1125     /**
   1126      * Update the {@link BluetoothMapAppParams} object message type filter mask to only contain
   1127      * message types supported by this mas instance.
   1128      * Could the folder be used in stead?
   1129      * @param appParams Reference to the object to update
   1130      * @param overwrite True: The msgType will be overwritten to match the message types supported
   1131      * by this MAS instance. False: any unsupported message types will be masked out.
   1132      */
   1133     private void setMsgTypeFilterParams(BluetoothMapAppParams appParams, boolean overwrite) {
   1134         int masFilterMask = 0;
   1135         if(!mEnableSmsMms) {
   1136             masFilterMask |= BluetoothMapAppParams.FILTER_NO_SMS_CDMA;
   1137             masFilterMask |= BluetoothMapAppParams.FILTER_NO_SMS_GSM;
   1138             masFilterMask |= BluetoothMapAppParams.FILTER_NO_MMS;
   1139         }
   1140         if(mAccount==null){
   1141             masFilterMask |= BluetoothMapAppParams.FILTER_NO_EMAIL;
   1142             masFilterMask |= BluetoothMapAppParams.FILTER_NO_IM;
   1143         } else {
   1144             if(!(mAccount.getType() == BluetoothMapUtils.TYPE.EMAIL)) {
   1145                 masFilterMask |= BluetoothMapAppParams.FILTER_NO_EMAIL;
   1146             }
   1147             if(!(mAccount.getType() == BluetoothMapUtils.TYPE.IM)) {
   1148                 masFilterMask |= BluetoothMapAppParams.FILTER_NO_IM;
   1149             }
   1150         }
   1151         if(overwrite) {
   1152             appParams.setFilterMessageType(masFilterMask);
   1153         } else {
   1154             int newMask = appParams.getFilterMessageType();
   1155             if(newMask == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
   1156                 appParams.setFilterMessageType(newMask);
   1157             } else {
   1158                 newMask |= masFilterMask;
   1159                 appParams.setFilterMessageType(newMask);
   1160             }
   1161         }
   1162     }
   1163 
   1164     /**
   1165      * Generate and send the Conversation listing response based on an application
   1166      * parameter header. This function call will block until complete or aborted
   1167      * by the peer. Fragmentation of packets larger than the obex packet size
   1168      * will be handled by this function.
   1169      *
   1170      * @param op
   1171      *            The OBEX operation.
   1172      * @param appParams
   1173      *            The application parameter header
   1174      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
   1175      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
   1176      */
   1177     private int sendConvoListingRsp(Operation op,
   1178                                     BluetoothMapAppParams appParams,
   1179                                     String folderName){
   1180         OutputStream outStream = null;
   1181         byte[] outBytes = null;
   1182         int maxChunkSize, bytesToWrite, bytesWritten = 0;
   1183         //boolean hasUnread = false;
   1184         HeaderSet replyHeaders = new HeaderSet();
   1185         BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
   1186         BluetoothMapConvoListing outList;
   1187         if(appParams == null){
   1188             appParams = new BluetoothMapAppParams();
   1189             appParams.setMaxListCount(1024);
   1190             appParams.setStartOffset(0);
   1191         }
   1192         // As the app parameters do not carry which message types to list, we set the filter here
   1193         // to all message types supported by this instance.
   1194         setMsgTypeFilterParams(appParams, true);
   1195 
   1196         // Check to see if we only need to send the size - hence no need to encode.
   1197         try {
   1198             // Open the OBEX body stream
   1199             outStream = op.openOutputStream();
   1200 
   1201             if(appParams.getMaxListCount() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
   1202                 appParams.setMaxListCount(1024);
   1203 
   1204             if(appParams.getStartOffset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
   1205                 appParams.setStartOffset(0);
   1206 
   1207             if(appParams.getMaxListCount() != 0) {
   1208                 outList = mOutContent.convoListing(appParams, false);
   1209                 outAppParams.setConvoListingSize(outList.getCount());
   1210                 // Generate the byte stream
   1211                 outBytes = outList.encode(); // Include thread ID for clients that supports it.
   1212                 if(D) Log.d(TAG, "outBytes size:"+ outBytes.length);
   1213             } else {
   1214                 outList = mOutContent.convoListing(appParams, true);
   1215                 outAppParams.setConvoListingSize(outList.getCount());
   1216                 if(mEnableSmsMms) {
   1217                     mOutContent.refreshSmsMmsConvoVersions();
   1218                 }
   1219                 if(mAccount != null) {
   1220                     mOutContent.refreshImEmailConvoVersions();
   1221                 }
   1222                 // Force update of version counter if needed
   1223                 mObserver.refreshConvoListVersionCounter();
   1224                 if(0 < (mRemoteFeatureMask &
   1225                         BluetoothMapUtils.MAP_FEATURE_CONVERSATION_VERSION_COUNTER_BIT)) {
   1226                     outAppParams.setConvoListingVerCounter(
   1227                             mMasInstance.getCombinedConvoListVersionCounter(), 0);
   1228                 }
   1229                 op.noBodyHeader();
   1230             }
   1231             if(D) Log.d(TAG, "outList size:"+ outList.getCount()
   1232                     + " MaxListCount: "+appParams.getMaxListCount());
   1233             outList = null; // We don't need it anymore - we might as well give it up for GC
   1234             outAppParams.setDatabaseIdentifier(0, mMasInstance.getDbIdentifier());
   1235 
   1236             // Build the application parameter header
   1237             // The MseTime is not in the CR - but I think it is missing.
   1238             outAppParams.setMseTime(Calendar.getInstance().getTime().getTime());
   1239             replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
   1240             op.sendHeaders(replyHeaders);
   1241 
   1242         } catch (IOException e) {
   1243             Log.w(TAG,"sendConvoListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
   1244             if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
   1245             if(mIsAborted == true) {
   1246                 if(D) Log.d(TAG, "sendConvoListingRsp Operation Aborted");
   1247                 return ResponseCodes.OBEX_HTTP_OK;
   1248             } else {
   1249                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
   1250             }
   1251         } catch (IllegalArgumentException e) {
   1252             Log.w(TAG,"sendConvoListingRsp: IllegalArgumentException" +
   1253                     " - sending OBEX_HTTP_BAD_REQUEST", e);
   1254             if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
   1255             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
   1256         }
   1257 
   1258         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
   1259         if(outBytes != null) {
   1260             try {
   1261                 while (bytesWritten < outBytes.length && mIsAborted == false) {
   1262                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
   1263                     outStream.write(outBytes, bytesWritten, bytesToWrite);
   1264                     bytesWritten += bytesToWrite;
   1265                 }
   1266             } catch (IOException e) {
   1267                 if(D) Log.w(TAG,e);
   1268                 // We were probably aborted or disconnected
   1269             } finally {
   1270                 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
   1271             }
   1272             if(bytesWritten != outBytes.length && !mIsAborted) {
   1273                 Log.w(TAG,"sendConvoListingRsp: bytesWritten != outBytes.length" +
   1274                         " - sending OBEX_HTTP_BAD_REQUEST");
   1275                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
   1276             }
   1277         } else {
   1278             if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
   1279         }
   1280         return ResponseCodes.OBEX_HTTP_OK;
   1281     }
   1282 
   1283     /**
   1284      * Generate and send the Folder listing response based on an application
   1285      * parameter header. This function call will block until complete or aborted
   1286      * by the peer. Fragmentation of packets larger than the obex packet size
   1287      * will be handled by this function.
   1288      *
   1289      * @param op
   1290      *            The OBEX operation.
   1291      * @param appParams
   1292      *            The application parameter header
   1293      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
   1294      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
   1295      */
   1296     private int sendFolderListingRsp(Operation op, BluetoothMapAppParams appParams){
   1297         OutputStream outStream = null;
   1298         byte[] outBytes = null;
   1299         BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
   1300         int maxChunkSize, bytesWritten = 0;
   1301         HeaderSet replyHeaders = new HeaderSet();
   1302         int bytesToWrite, maxListCount, listStartOffset;
   1303         if(appParams == null){
   1304             appParams = new BluetoothMapAppParams();
   1305             appParams.setMaxListCount(1024);
   1306         }
   1307 
   1308         if(V)
   1309             Log.v(TAG,"sendFolderList for " + mCurrentFolder.getName());
   1310 
   1311         try {
   1312             maxListCount = appParams.getMaxListCount();
   1313             listStartOffset = appParams.getStartOffset();
   1314 
   1315             if(listStartOffset == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
   1316                 listStartOffset = 0;
   1317 
   1318             if(maxListCount == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
   1319                 maxListCount = 1024;
   1320 
   1321             if(maxListCount != 0)
   1322             {
   1323                 outBytes = mCurrentFolder.encode(listStartOffset, maxListCount);
   1324                 outStream = op.openOutputStream();
   1325             } else {
   1326                 // ESR08 specified that this shall only be included for MaxListCount=0
   1327                 outAppParams.setFolderListingSize(mCurrentFolder.getSubFolderCount());
   1328                 op.noBodyHeader();
   1329             }
   1330 
   1331             // Build and set the application parameter header
   1332             replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
   1333             op.sendHeaders(replyHeaders);
   1334 
   1335         } catch (IOException e1) {
   1336             Log.w(TAG,"sendFolderListingRsp: IOException" +
   1337                     " - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
   1338             if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
   1339             if(mIsAborted == true) {
   1340                 if(D) Log.d(TAG, "sendFolderListingRsp Operation Aborted");
   1341                 return ResponseCodes.OBEX_HTTP_OK;
   1342             } else {
   1343                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
   1344             }
   1345         } catch (IllegalArgumentException e1) {
   1346             Log.w(TAG,"sendFolderListingRsp: IllegalArgumentException" +
   1347                     " - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
   1348             if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
   1349             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
   1350         }
   1351 
   1352         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
   1353 
   1354         if(outBytes != null) {
   1355             try {
   1356                 while (bytesWritten < outBytes.length && mIsAborted == false) {
   1357                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
   1358                     outStream.write(outBytes, bytesWritten, bytesToWrite);
   1359                     bytesWritten += bytesToWrite;
   1360                 }
   1361             } catch (IOException e) {
   1362                 // We were probably aborted or disconnected
   1363             } finally {
   1364                 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
   1365             }
   1366             if(V)
   1367                 Log.v(TAG,"sendFolderList sent " + bytesWritten+" bytes out of "+ outBytes.length);
   1368             if(bytesWritten == outBytes.length || mIsAborted)
   1369                 return ResponseCodes.OBEX_HTTP_OK;
   1370             else
   1371                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
   1372         }
   1373 
   1374         return ResponseCodes.OBEX_HTTP_OK;
   1375     }
   1376 
   1377     /**
   1378      * Generate and send the get MAS Instance Information response based on an MAS Instance
   1379      *
   1380      * @param op
   1381      *            The OBEX operation.
   1382      * @param appParams
   1383      *            The application parameter header
   1384      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
   1385      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
   1386      */
   1387     private int sendMASInstanceInformationRsp(Operation op, BluetoothMapAppParams appParams){
   1388 
   1389         OutputStream outStream = null;
   1390         byte[] outBytes = null;
   1391         String outString = null;
   1392         int maxChunkSize, bytesToWrite, bytesWritten = 0;
   1393 
   1394         try {
   1395             if(mMasId == appParams.getMasInstanceId()) {
   1396                 if(mAccount != null) {
   1397                     if(mAccount.getType() == TYPE.EMAIL) {
   1398                         outString = (mAccount.getName() != null) ? mAccount.getName() :
   1399                             BluetoothMapMasInstance.TYPE_EMAIL_STR;
   1400                     } else if(mAccount.getType() == TYPE.IM){
   1401                         outString = mAccount.getUciFull();
   1402                         if(outString == null) {
   1403                             String uci = mAccount.getUci();
   1404                             // TODO: Do we need to align this with HF/PBAP
   1405                             StringBuilder sb =
   1406                                     new StringBuilder(uci == null ? 5 : 5 + uci.length());
   1407                             sb.append("un");
   1408                             if(mMasId < 10) {
   1409                                 sb.append("00");
   1410                             } else if(mMasId < 100) {
   1411                                 sb.append("0");
   1412                             }
   1413                             sb.append(mMasId);
   1414                             if(uci != null) {
   1415                                 sb.append(":").append(uci);
   1416                             }
   1417                             outString = sb.toString();
   1418                         }
   1419                     }
   1420                 } else {
   1421                     outString = BluetoothMapMasInstance.TYPE_SMS_MMS_STR;
   1422                     // TODO: Add phone number if possible
   1423                 }
   1424             } else {
   1425                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
   1426             }
   1427 
   1428             /* Ensure byte array max length is 200 containing valid UTF-8 characters */
   1429             outBytes = BluetoothMapUtils.truncateUtf8StringToBytearray(outString,
   1430                     MAS_INSTANCE_INFORMATION_LENGTH);
   1431 
   1432             // Open the OBEX body stream
   1433             outStream = op.openOutputStream();
   1434 
   1435         } catch (IOException e) {
   1436             Log.w(TAG,"sendMASInstanceInformationRsp: IOException" +
   1437                     " - sending OBEX_HTTP_BAD_REQUEST", e);
   1438             if(mIsAborted == true) {
   1439                 if(D) Log.d(TAG, "sendMASInstanceInformationRsp Operation Aborted");
   1440                 return ResponseCodes.OBEX_HTTP_OK;
   1441             } else {
   1442                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
   1443             }
   1444         }
   1445 
   1446         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
   1447 
   1448         if(outBytes != null) {
   1449             try {
   1450                 while (bytesWritten < outBytes.length && mIsAborted == false) {
   1451                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
   1452                     outStream.write(outBytes, bytesWritten, bytesToWrite);
   1453                     bytesWritten += bytesToWrite;
   1454                 }
   1455             } catch (IOException e) {
   1456                 // We were probably aborted or disconnected
   1457             } finally {
   1458                 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
   1459             }
   1460             if(V)
   1461                 Log.v(TAG,"sendMASInstanceInformationRsp sent " + bytesWritten +
   1462                         " bytes out of "+ outBytes.length);
   1463             if(bytesWritten == outBytes.length || mIsAborted)
   1464                 return ResponseCodes.OBEX_HTTP_OK;
   1465             else
   1466                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
   1467         }
   1468         return ResponseCodes.OBEX_HTTP_OK;
   1469     }
   1470 
   1471     /**
   1472      * Generate and send the get message response based on an application
   1473      * parameter header and a handle.
   1474      *
   1475      * @param op
   1476      *            The OBEX operation.
   1477      * @param handle
   1478      *            The handle of the requested message
   1479      * @param appParams
   1480      *            The application parameter header
   1481      * @param version
   1482      *              The string representation of the version number(i.e. "1.0")
   1483      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
   1484      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
   1485      */
   1486     private int sendGetMessageRsp(Operation op, String handle,
   1487             BluetoothMapAppParams appParams, String version){
   1488         OutputStream outStream = null;
   1489         byte[] outBytes = null;
   1490         int maxChunkSize, bytesToWrite, bytesWritten = 0;
   1491 
   1492         try {
   1493             outBytes = mOutContent.getMessage(handle, appParams, mCurrentFolder, version);
   1494             outStream = op.openOutputStream();
   1495 
   1496             // If it is a fraction request of Email message, set header before responding
   1497             if ((BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.EMAIL)||
   1498                     (BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.IM))) &&
   1499                     (appParams.getFractionRequest() ==
   1500                     BluetoothMapAppParams.FRACTION_REQUEST_FIRST)) {
   1501                 BluetoothMapAppParams outAppParams  = new BluetoothMapAppParams();
   1502                 HeaderSet replyHeaders = new HeaderSet();
   1503                 outAppParams.setFractionDeliver(BluetoothMapAppParams.FRACTION_DELIVER_LAST);
   1504                 // Build and set the application parameter header
   1505                 replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER,
   1506                         outAppParams.EncodeParams());
   1507                 op.sendHeaders(replyHeaders);
   1508                 if(V) Log.v(TAG,"sendGetMessageRsp fractionRequest - " +
   1509                         "set FRACTION_DELIVER_LAST header");
   1510             }
   1511 
   1512         } catch (IOException e) {
   1513             Log.w(TAG,"sendGetMessageRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
   1514             if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
   1515             if(mIsAborted == true) {
   1516                 if(D) Log.d(TAG, "sendGetMessageRsp Operation Aborted");
   1517                 return ResponseCodes.OBEX_HTTP_OK;
   1518             } else {
   1519                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
   1520             }
   1521         } catch (IllegalArgumentException e) {
   1522             Log.w(TAG,"sendGetMessageRsp: IllegalArgumentException (e.g. invalid handle) - " +
   1523                     "sending OBEX_HTTP_BAD_REQUEST", e);
   1524             if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
   1525             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
   1526         }
   1527 
   1528         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
   1529 
   1530         if(outBytes != null) {
   1531             try {
   1532                 while (bytesWritten < outBytes.length && mIsAborted == false) {
   1533                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
   1534                     outStream.write(outBytes, bytesWritten, bytesToWrite);
   1535                     bytesWritten += bytesToWrite;
   1536                 }
   1537             } catch (IOException e) {
   1538                 // We were probably aborted or disconnected
   1539                 if(D && e.getMessage().equals("Abort Received")) {
   1540                     Log.w(TAG, "getMessage() Aborted...", e);
   1541                 }
   1542             } finally {
   1543                 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
   1544             }
   1545             if(bytesWritten == outBytes.length || mIsAborted)
   1546                 return ResponseCodes.OBEX_HTTP_OK;
   1547             else
   1548                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
   1549         }
   1550 
   1551         return ResponseCodes.OBEX_HTTP_OK;
   1552     }
   1553 
   1554     @Override
   1555     public int onDelete(HeaderSet request, HeaderSet reply) {
   1556         if(D) Log.v(TAG, "onDelete() " + request.toString());
   1557         mIsAborted = false;
   1558         notifyUpdateWakeLock();
   1559         String type, name;
   1560         byte[] appParamRaw;
   1561         BluetoothMapAppParams appParams = null;
   1562 
   1563         /* TODO: If this is to be placed here, we need to cleanup - e.g. the exception handling */
   1564         try {
   1565             type = (String)request.getHeader(HeaderSet.TYPE);
   1566 
   1567             name = (String)request.getHeader(HeaderSet.NAME);
   1568             appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
   1569             if(appParamRaw != null)
   1570                 appParams = new BluetoothMapAppParams(appParamRaw);
   1571             if(D) Log.d(TAG,"type = " + type + ", name = " + name);
   1572             if(type.equals(TYPE_SET_NOTIFICATION_FILTER)) {
   1573                 if(V) {
   1574                     Log.d(TAG,"TYPE_SET_NOTIFICATION_FILTER: NotificationFilter: "
   1575                             + appParams.getNotificationFilter());
   1576                 }
   1577                 mObserver.setNotificationFilter(appParams.getNotificationFilter());
   1578                 return ResponseCodes.OBEX_HTTP_OK;
   1579             }  else if (type.equals(TYPE_SET_OWNER_STATUS)) {
   1580                 if(V) {
   1581                     Log.d(TAG,"TYPE_SET_OWNER_STATUS:" +
   1582                           " PresenceAvailability " + appParams.getPresenceAvailability() +
   1583                           ", PresenceStatus: " + appParams.getPresenceStatus() +
   1584                           ", LastActivity: " + appParams.getLastActivityString() +
   1585                           ", ChatStatus: " + appParams.getChatState() +
   1586                           ", ChatStatusConvoId: " + appParams.getChatStateConvoIdString());
   1587                 }
   1588                 return setOwnerStatus(name, appParams);
   1589             }
   1590 
   1591         } catch (RemoteException e){
   1592             //reload the providerClient and return error
   1593             try {
   1594                 mProviderClient = acquireUnstableContentProviderOrThrow();
   1595             }catch (RemoteException e2){
   1596                 //should not happen
   1597             }
   1598             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
   1599         }catch (Exception e) {
   1600 
   1601             if(D) {
   1602                 Log.e(TAG, "Exception occured while handling request",e);
   1603             } else {
   1604                 Log.e(TAG, "Exception occured while handling request");
   1605             }
   1606             if(mIsAborted) {
   1607                 return ResponseCodes.OBEX_HTTP_OK;
   1608             } else {
   1609                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
   1610             }
   1611         }
   1612         return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
   1613     }
   1614 
   1615     private void notifyUpdateWakeLock() {
   1616         if(mCallback != null) {
   1617             Message msg = Message.obtain(mCallback);
   1618             msg.what = BluetoothMapService.MSG_ACQUIRE_WAKE_LOCK;
   1619             msg.sendToTarget();
   1620         }
   1621     }
   1622 
   1623     private static final void logHeader(HeaderSet hs) {
   1624         Log.v(TAG, "Dumping HeaderSet " + hs.toString());
   1625         try {
   1626             Log.v(TAG, "CONNECTION_ID : " + hs.getHeader(HeaderSet.CONNECTION_ID));
   1627             Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME));
   1628             Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE));
   1629             Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET));
   1630             Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO));
   1631             Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER));
   1632         } catch (IOException e) {
   1633             Log.e(TAG, "dump HeaderSet error " + e);
   1634         }
   1635         Log.v(TAG, "NEW!!! Dumping HeaderSet END");
   1636     }
   1637 }
   1638