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.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.RemoteException;
     26 import com.android.bluetooth.mapapi.BluetoothMapContract;
     27 import android.text.format.DateUtils;
     28 import android.util.Log;
     29 
     30 import com.android.bluetooth.map.BluetoothMapUtils;
     31 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
     32 
     33 import java.io.IOException;
     34 import java.io.InputStream;
     35 import java.io.OutputStream;
     36 import java.text.ParseException;
     37 import java.util.Arrays;
     38 import java.util.Calendar;
     39 
     40 import javax.obex.HeaderSet;
     41 import javax.obex.Operation;
     42 import javax.obex.ResponseCodes;
     43 import javax.obex.ServerRequestHandler;
     44 
     45 
     46 public class BluetoothMapObexServer extends ServerRequestHandler {
     47 
     48     private static final String TAG = "BluetoothMapObexServer";
     49 
     50     private static final boolean D = BluetoothMapService.DEBUG;
     51     private static final boolean V = BluetoothMapService.VERBOSE;
     52 
     53     private static final int UUID_LENGTH = 16;
     54 
     55     private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS;
     56 
     57     /* OBEX header and value used to detect clients that support threadId in the message listing. */
     58     private static final int THREADED_MAIL_HEADER_ID = 0xFA;
     59     private static final long THREAD_MAIL_KEY = 0x534c5349;
     60 
     61     // 128 bit UUID for MAP
     62     private static final byte[] MAP_TARGET = new byte[] {
     63              (byte)0xBB, (byte)0x58, (byte)0x2B, (byte)0x40,
     64              (byte)0x42, (byte)0x0C, (byte)0x11, (byte)0xDB,
     65              (byte)0xB0, (byte)0xDE, (byte)0x08, (byte)0x00,
     66              (byte)0x20, (byte)0x0C, (byte)0x9A, (byte)0x66
     67              };
     68 
     69     /* Message types */
     70     private static final String TYPE_GET_FOLDER_LISTING  = "x-obex/folder-listing";
     71     private static final String TYPE_GET_MESSAGE_LISTING = "x-bt/MAP-msg-listing";
     72     private static final String TYPE_MESSAGE             = "x-bt/message";
     73     private static final String TYPE_SET_MESSAGE_STATUS  = "x-bt/messageStatus";
     74     private static final String TYPE_SET_NOTIFICATION_REGISTRATION = "x-bt/MAP-NotificationRegistration";
     75     private static final String TYPE_MESSAGE_UPDATE      = "x-bt/MAP-messageUpdate";
     76 
     77     private BluetoothMapFolderElement mCurrentFolder;
     78 
     79     private BluetoothMapContentObserver mObserver = null;
     80 
     81     private Handler mCallback = null;
     82 
     83     private Context mContext;
     84 
     85     private boolean mIsAborted = false;
     86 
     87     BluetoothMapContent mOutContent;
     88 
     89     private String mBaseEmailUriString = null;
     90     private long mAccountId = 0;
     91     private BluetoothMapEmailSettingsItem mAccount = null;
     92     private Uri mEmailFolderUri = null;
     93 
     94     private int mMasId = 0;
     95 
     96     private boolean mEnableSmsMms = false;
     97     private boolean mThreadIdSupport = false; // true if peer supports threadId in msg listing
     98     private String mAuthority;
     99     private ContentResolver mResolver;
    100     private ContentProviderClient mProviderClient = null;
    101 
    102     public BluetoothMapObexServer(Handler callback,
    103                                   Context context,
    104                                   BluetoothMapContentObserver observer,
    105                                   int masId,
    106                                   BluetoothMapEmailSettingsItem account,
    107                                   boolean enableSmsMms) throws RemoteException {
    108         super();
    109         mCallback = callback;
    110         mContext = context;
    111         mObserver = observer;
    112         mEnableSmsMms = enableSmsMms;
    113         mAccount = account;
    114         mMasId = masId;
    115 
    116         if(account != null && account.getProviderAuthority() != null) {
    117             mAccountId = account.getAccountId();
    118             mAuthority = account.getProviderAuthority();
    119             mResolver = mContext.getContentResolver();
    120             if (D) Log.d(TAG, "BluetoothMapObexServer(): accountId=" + mAccountId);
    121             mBaseEmailUriString = account.mBase_uri + "/";
    122             if (D) Log.d(TAG, "BluetoothMapObexServer(): emailBaseUri=" + mBaseEmailUriString);
    123             mEmailFolderUri = BluetoothMapContract.buildFolderUri(mAuthority,
    124                                                                   Long.toString(mAccountId));
    125             if (D) Log.d(TAG, "BluetoothMapObexServer(): mEmailFolderUri=" + mEmailFolderUri);
    126             mProviderClient = acquireUnstableContentProviderOrThrow();
    127         }
    128 
    129         buildFolderStructure(); /* Build the default folder structure, and set
    130                                    mCurrentFolder to root folder */
    131         mObserver.setFolderStructure(mCurrentFolder.getRoot());
    132 
    133         mOutContent = new BluetoothMapContent(mContext, mBaseEmailUriString);
    134 
    135     }
    136 
    137     /**
    138      *
    139      */
    140     private ContentProviderClient acquireUnstableContentProviderOrThrow() throws RemoteException{
    141         ContentProviderClient providerClient = mResolver.acquireUnstableContentProviderClient(mAuthority);
    142         if (providerClient == null) {
    143             throw new RemoteException("Failed to acquire provider for " + mAuthority);
    144         }
    145         providerClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
    146         return providerClient;
    147     }
    148 
    149     /**
    150      * Build the default minimal folder structure, as defined in the MAP specification.
    151      */
    152     private void buildFolderStructure() throws RemoteException{
    153         mCurrentFolder = new BluetoothMapFolderElement("root", null); // This will be the root element
    154         BluetoothMapFolderElement tmpFolder;
    155         tmpFolder = mCurrentFolder.addFolder("telecom"); // root/telecom
    156         tmpFolder = tmpFolder.addFolder("msg");          // root/telecom/msg
    157 
    158         addBaseFolders(tmpFolder); // Add the mandatory folders
    159 
    160         if(mEnableSmsMms) {
    161             addSmsMmsFolders(tmpFolder);
    162         }
    163         if(mEmailFolderUri != null) {
    164             if (D) Log.d(TAG, "buildFolderStructure(): " + mEmailFolderUri.toString());
    165             addEmailFolders(tmpFolder);
    166         }
    167     }
    168 
    169     /**
    170      * Add
    171      * @param root
    172      */
    173     private void addBaseFolders(BluetoothMapFolderElement root) {
    174         root.addFolder(BluetoothMapContract.FOLDER_NAME_INBOX);                    // root/telecom/msg/inbox
    175         root.addFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX);
    176         root.addFolder(BluetoothMapContract.FOLDER_NAME_SENT);
    177         root.addFolder(BluetoothMapContract.FOLDER_NAME_DELETED);
    178     }
    179 
    180 
    181     /**
    182      * Add
    183      * @param root
    184      */
    185     private void addSmsMmsFolders(BluetoothMapFolderElement root) {
    186         root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_INBOX);                    // root/telecom/msg/inbox
    187         root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX);
    188         root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_SENT);
    189         root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_DELETED);
    190         root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_DRAFT);
    191     }
    192 
    193 
    194     /**
    195      * Recursively adds folders based on the folders in the email content provider.
    196      *       Add a content observer? - to refresh the folder list if any change occurs.
    197      *       Consider simply deleting the entire table, and then rebuild using buildFolderStructure()
    198      *       WARNING: there is no way to notify the client about these changes - hence
    199      *       we need to either keep the folder structure constant, disconnect or fail anything
    200      *       referring to currentFolder.
    201      *       It is unclear what to set as current folder to be able to go one level up...
    202      *       The best solution would be to keep the folder structure constant during a connection.
    203      * @param folder the parent folder to which subFolders needs to be added. The
    204      *        folder.getEmailFolderId() will be used to query sub-folders.
    205      *        Use a parentFolder with id -1 to get all folders from root.
    206      */
    207     private void addEmailFolders(BluetoothMapFolderElement parentFolder) throws RemoteException {
    208         // Select all parent folders
    209         BluetoothMapFolderElement newFolder;
    210 
    211         String where = BluetoothMapContract.FolderColumns.PARENT_FOLDER_ID +
    212                         " = " + parentFolder.getEmailFolderId();
    213         Cursor c = mProviderClient.query(mEmailFolderUri,
    214                         BluetoothMapContract.BT_FOLDER_PROJECTION, where, null, null);
    215         if (c != null) {
    216             c.moveToPosition(-1);
    217             while (c.moveToNext()) {
    218                 String name = c.getString(c.getColumnIndex(BluetoothMapContract.FolderColumns.NAME));
    219                 long id = c.getLong(c.getColumnIndex(BluetoothMapContract.FolderColumns._ID));
    220                 newFolder = parentFolder.addEmailFolder(name, id);
    221                 addEmailFolders(newFolder); // Use recursion to add any sub folders
    222             }
    223             c.close();
    224         } else {
    225             if (D) Log.d(TAG, "addEmailFolders(): no elements found");
    226         }
    227     }
    228 
    229     @Override
    230     public int onConnect(final HeaderSet request, HeaderSet reply) {
    231         if (D) Log.d(TAG, "onConnect():");
    232         if (V) logHeader(request);
    233         mThreadIdSupport = false; // Always assume not supported at new connect.
    234         notifyUpdateWakeLock();
    235         Long threadedMailKey = null;
    236         try {
    237             byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET);
    238             threadedMailKey = (Long)request.getHeader(THREADED_MAIL_HEADER_ID);
    239             if (uuid == null) {
    240                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    241             }
    242             if (D) Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid));
    243 
    244             if (uuid.length != UUID_LENGTH) {
    245                 Log.w(TAG, "Wrong UUID length");
    246                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    247             }
    248             for (int i = 0; i < UUID_LENGTH; i++) {
    249                 if (uuid[i] != MAP_TARGET[i]) {
    250                     Log.w(TAG, "Wrong UUID");
    251                     return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    252                 }
    253             }
    254             reply.setHeader(HeaderSet.WHO, uuid);
    255         } catch (IOException e) {
    256             Log.e(TAG,"Exception during onConnect:", e);
    257             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    258         }
    259 
    260         try {
    261             byte[] remote = (byte[])request.getHeader(HeaderSet.WHO);
    262             if (remote != null) {
    263                 if (D) Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote));
    264                 reply.setHeader(HeaderSet.TARGET, remote);
    265             }
    266             if(threadedMailKey != null && threadedMailKey.longValue() == THREAD_MAIL_KEY)
    267             {
    268                 /* If the client provides the correct key we enable threaded e-mail support
    269                  * and reply to the client that we support the requested feature.
    270                  * This is currently an Android only feature. */
    271                 mThreadIdSupport = true;
    272                 reply.setHeader(THREADED_MAIL_HEADER_ID, THREAD_MAIL_KEY);
    273             }
    274         } catch (IOException e) {
    275             Log.e(TAG,"Exception during onConnect:", e);
    276             mThreadIdSupport = false;
    277             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    278         }
    279 
    280         if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " +
    281                 "MSG_SESSION_ESTABLISHED msg.");
    282 
    283         if(mCallback != null) {
    284             Message msg = Message.obtain(mCallback);
    285             msg.what = BluetoothMapService.MSG_SESSION_ESTABLISHED;
    286             msg.sendToTarget();
    287         }
    288 
    289         return ResponseCodes.OBEX_HTTP_OK;
    290     }
    291 
    292     @Override
    293     public void onDisconnect(final HeaderSet req, final HeaderSet resp) {
    294         if (D) Log.d(TAG, "onDisconnect(): enter");
    295         if (V) logHeader(req);
    296         notifyUpdateWakeLock();
    297         resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
    298         if (mCallback != null) {
    299             Message msg = Message.obtain(mCallback);
    300             msg.what = BluetoothMapService.MSG_SESSION_DISCONNECTED;
    301             msg.sendToTarget();
    302             if (V) Log.v(TAG, "onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out.");
    303         }
    304     }
    305 
    306     @Override
    307     public int onAbort(HeaderSet request, HeaderSet reply) {
    308         if (D) Log.d(TAG, "onAbort(): enter.");
    309         notifyUpdateWakeLock();
    310         mIsAborted = true;
    311         return ResponseCodes.OBEX_HTTP_OK;
    312     }
    313 
    314     @Override
    315     public int onPut(final Operation op) {
    316         if (D) Log.d(TAG, "onPut(): enter");
    317         mIsAborted = false;
    318         notifyUpdateWakeLock();
    319         HeaderSet request = null;
    320         String type, name;
    321         byte[] appParamRaw;
    322         BluetoothMapAppParams appParams = null;
    323 
    324         try {
    325             request = op.getReceivedHeader();
    326             type = (String)request.getHeader(HeaderSet.TYPE);
    327 
    328             name = (String)request.getHeader(HeaderSet.NAME);
    329             appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
    330             if(appParamRaw != null)
    331                 appParams = new BluetoothMapAppParams(appParamRaw);
    332             if(D) Log.d(TAG,"type = " + type + ", name = " + name);
    333             if (type.equals(TYPE_MESSAGE_UPDATE)) {
    334                 if(V) {
    335                     Log.d(TAG,"TYPE_MESSAGE_UPDATE:");
    336                 }
    337                 return updateInbox();
    338             }else if(type.equals(TYPE_SET_NOTIFICATION_REGISTRATION)) {
    339                 if(V) {
    340                     Log.d(TAG,"TYPE_SET_NOTIFICATION_REGISTRATION: NotificationStatus: "
    341                             + appParams.getNotificationStatus());
    342                 }
    343                 return mObserver.setNotificationRegistration(appParams.getNotificationStatus());
    344             }else if(type.equals(TYPE_SET_MESSAGE_STATUS)) {
    345                 if(V) {
    346                     Log.d(TAG,"TYPE_SET_MESSAGE_STATUS: StatusIndicator: "
    347                             + appParams.getStatusIndicator()
    348                             + ", StatusValue: " + appParams.getStatusValue());
    349                 }
    350                 return setMessageStatus(name, appParams);
    351             } else if (type.equals(TYPE_MESSAGE)) {
    352                 if(V) {
    353                     Log.d(TAG,"TYPE_MESSAGE: Transparet: " + appParams.getTransparent()
    354                             + ", retry: " + appParams.getRetry()
    355                             + ", charset: " + appParams.getCharset());
    356                 }
    357                 return pushMessage(op, name, appParams);
    358             }
    359         } catch (RemoteException e){
    360             //reload the providerClient and return error
    361             try {
    362                 mProviderClient = acquireUnstableContentProviderOrThrow();
    363             }catch (RemoteException e2){
    364                 //should not happen
    365             }
    366             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    367         }catch (Exception e) {
    368 
    369             if(D) {
    370                 Log.e(TAG, "Exception occured while handling request",e);
    371             } else {
    372                 Log.e(TAG, "Exception occured while handling request");
    373             }
    374             if(mIsAborted) {
    375                 return ResponseCodes.OBEX_HTTP_OK;
    376             } else {
    377                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    378             }
    379         }
    380         return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    381     }
    382 
    383     private int updateInbox() throws RemoteException{
    384         if (mAccount != null) {
    385             BluetoothMapFolderElement inboxFolder = mCurrentFolder.getEmailFolderByName(
    386                     BluetoothMapContract.FOLDER_NAME_INBOX);
    387             if (inboxFolder != null) {
    388                 long accountId = mAccountId;
    389                 if (D) Log.d(TAG,"updateInbox inbox=" + inboxFolder.getName() + "id="
    390                         + inboxFolder.getEmailFolderId());
    391 
    392                 final Bundle extras = new Bundle(2);
    393                 if (accountId != -1) {
    394                     if (D) Log.d(TAG,"updateInbox accountId=" + accountId);
    395                     extras.putLong(BluetoothMapContract.EXTRA_UPDATE_FOLDER_ID,
    396                             inboxFolder.getEmailFolderId());
    397                     extras.putLong(BluetoothMapContract.EXTRA_UPDATE_ACCOUNT_ID, accountId);
    398                 } else {
    399                     // Only error code allowed on an UpdateInbox is OBEX_HTTP_NOT_IMPLEMENTED,
    400                     // i.e. if e.g. update not allowed on the mailbox
    401                     if (D) Log.d(TAG,"updateInbox accountId=0 -> OBEX_HTTP_NOT_IMPLEMENTED");
    402                     return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
    403                 }
    404 
    405                 Uri emailUri = Uri.parse(mBaseEmailUriString);
    406                 if (D) Log.d(TAG,"updateInbox in: " + emailUri.toString());
    407                 try {
    408                     if (D) Log.d(TAG,"updateInbox call()...");
    409                     Bundle myBundle = mProviderClient.call(BluetoothMapContract.METHOD_UPDATE_FOLDER, null, extras);
    410                     if (myBundle != null)
    411                         return ResponseCodes.OBEX_HTTP_OK;
    412                     else {
    413                         if (D) Log.d(TAG,"updateInbox call failed");
    414                         return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
    415                     }
    416                 } catch (RemoteException e){
    417                     mProviderClient = acquireUnstableContentProviderOrThrow();
    418                     return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    419                 }catch (NullPointerException e) {
    420                     if(D) Log.e(TAG, "UpdateInbox - if uri or method is null", e);
    421                     return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    422 
    423                 } catch (IllegalArgumentException e) {
    424                     if(D) Log.e(TAG, "UpdateInbox - if uri is not known", e);
    425                     return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    426                 }
    427             }
    428         }
    429 
    430         return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
    431     }
    432 
    433      private BluetoothMapFolderElement getFolderElementFromName(String folderName) {
    434         BluetoothMapFolderElement folderElement = null;
    435 
    436         if(folderName == null || folderName.trim().isEmpty() ) {
    437             folderElement = mCurrentFolder;
    438             if(D) Log.d(TAG, "no folder name supplied, setting folder to current: "
    439                              + folderElement.getName());
    440         } else {
    441             folderElement = mCurrentFolder.getSubFolder(folderName);
    442             if(D) Log.d(TAG, "Folder name: " + folderName + " resulted in this element: "
    443                     + folderElement.getName());
    444         }
    445         return folderElement;
    446     }
    447 
    448     private int pushMessage(final Operation op, String folderName, BluetoothMapAppParams appParams) {
    449         if(appParams.getCharset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
    450             if(D) Log.d(TAG, "pushMessage: Missing charset - unable to decode message content. " +
    451                     "appParams.getCharset() = " + appParams.getCharset());
    452             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    453         }
    454         InputStream bMsgStream = null;
    455         try {
    456             BluetoothMapFolderElement folderElement = getFolderElementFromName(folderName);
    457             if(folderElement == null) {
    458                 Log.w(TAG,"pushMessage: folderElement == null - sending OBEX_HTTP_PRECON_FAILED");
    459                 return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    460             } else {
    461                 folderName = folderElement.getName();
    462             }
    463             if (!folderName.equals(BluetoothMapContract.FOLDER_NAME_OUTBOX) &&
    464                     !folderName.equals(BluetoothMapContract.FOLDER_NAME_DRAFT)) {
    465                 if(D) Log.d(TAG, "pushMessage: Is only allowed to outbox and draft. " +
    466                         "folderName=" + folderName);
    467                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    468             }
    469 
    470             /*  - Read out the message
    471              *  - Decode into a bMessage
    472              *  - send it.
    473              */
    474             BluetoothMapbMessage message;
    475             bMsgStream = op.openInputStream();
    476             // Decode the messageBody
    477             message = BluetoothMapbMessage.parse(bMsgStream, appParams.getCharset());
    478             // Send message
    479             if (mObserver == null || message == null) {
    480                 // Should not happen except at shutdown.
    481                 if(D) Log.w(TAG, "mObserver or parsed message not available" );
    482                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    483             }
    484 
    485             if ((message.getType().equals(TYPE.EMAIL) && (folderElement.getEmailFolderId() == -1)) ||
    486                 ((message.getType().equals(TYPE.SMS_GSM) || message.getType().equals(TYPE.SMS_CDMA) ||
    487                   message.getType().equals(TYPE.MMS)) && !folderElement.hasSmsMmsContent()) ) {
    488                 if(D) Log.w(TAG, "Wrong message type recieved" );
    489                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    490             }
    491 
    492             long handle = mObserver.pushMessage(message, folderElement, appParams, mBaseEmailUriString);
    493             if (D) Log.d(TAG, "pushMessage handle: " + handle);
    494             if (handle < 0) {
    495                 if(D) Log.w(TAG, "Message  handle not created" );
    496                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
    497             }
    498             HeaderSet replyHeaders = new HeaderSet();
    499             String handleStr = BluetoothMapUtils.getMapHandle(handle, message.getType());
    500             if (D) Log.d(TAG, "handleStr: " + handleStr + " message.getType(): " + message.getType());
    501             replyHeaders.setHeader(HeaderSet.NAME, handleStr);
    502             op.sendHeaders(replyHeaders);
    503 
    504         } catch (RemoteException e) {
    505             //reload the providerClient and return error
    506             try {
    507                 mProviderClient = acquireUnstableContentProviderOrThrow();
    508             }catch (RemoteException e2){
    509                 //should not happen
    510             }
    511             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    512         } catch (IllegalArgumentException e) {
    513             if (D) Log.e(TAG, "Wrongly formatted bMessage received", e);
    514             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    515         } catch (IOException e) {
    516             if (D) Log.e(TAG, "Exception occured: ", e);
    517             if(mIsAborted == true) {
    518                 if(D) Log.d(TAG, "PushMessage Operation Aborted");
    519                 return ResponseCodes.OBEX_HTTP_OK;
    520             } else {
    521                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    522             }
    523         } catch (Exception e) {
    524             if (D) Log.e(TAG, "Exception:", e);
    525             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    526         } finally {
    527             if(bMsgStream != null) {
    528                 try {
    529                     bMsgStream.close();
    530                 } catch (IOException e) {}
    531             }
    532         }
    533         return ResponseCodes.OBEX_HTTP_OK;
    534     }
    535 
    536     private int setMessageStatus(String msgHandle, BluetoothMapAppParams appParams) {
    537         int indicator = appParams.getStatusIndicator();
    538         int value = appParams.getStatusValue();
    539         long handle;
    540         BluetoothMapUtils.TYPE msgType;
    541 
    542         if(indicator == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
    543            value == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
    544            msgHandle == null) {
    545             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    546         }
    547         if (mObserver == null) {
    548             if(D) Log.d(TAG, "Error: no mObserver!");
    549             return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
    550         }
    551 
    552         try {
    553             handle = BluetoothMapUtils.getCpHandle(msgHandle);
    554             msgType = BluetoothMapUtils.getMsgTypeFromHandle(msgHandle);
    555             if(D)Log.d(TAG,"setMessageStatus. Handle:" + handle+", MsgType: "+ msgType);
    556         } catch (NumberFormatException e) {
    557             Log.w(TAG, "Wrongly formatted message handle: " + msgHandle);
    558             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    559         }
    560 
    561         if( indicator == BluetoothMapAppParams.STATUS_INDICATOR_DELETED) {
    562             if (!mObserver.setMessageStatusDeleted(handle, msgType, mCurrentFolder,
    563                     mBaseEmailUriString, value)) {
    564                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    565             }
    566         } else /* BluetoothMapAppParams.STATUS_INDICATOR_READ */ {
    567             try{
    568             if (!mObserver.setMessageStatusRead(handle, msgType, mBaseEmailUriString, value)) {
    569                 if(D)Log.d(TAG,"not able to update the message");
    570                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    571             }
    572             }catch(RemoteException e) {
    573                 if(D) Log.e(TAG,"Error in setMessageStatusRead()", e);
    574                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    575             }
    576         }
    577         return ResponseCodes.OBEX_HTTP_OK;
    578     }
    579 
    580     @Override
    581     public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup,
    582             final boolean create) {
    583         String folderName;
    584         BluetoothMapFolderElement folder;
    585         notifyUpdateWakeLock();
    586         try {
    587             folderName = (String)request.getHeader(HeaderSet.NAME);
    588         } catch (Exception e) {
    589             if(D) {
    590                 Log.e(TAG, "request headers error" , e);
    591             } else {
    592                 Log.e(TAG, "request headers error");
    593             }
    594             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    595         }
    596 
    597         if (V) logHeader(request);
    598         if (D) Log.d(TAG, "onSetPath name is " + folderName + " backup: " + backup
    599                      + " create: " + create);
    600 
    601         if(backup == true){
    602             if(mCurrentFolder.getParent() != null)
    603                 mCurrentFolder = mCurrentFolder.getParent();
    604             else
    605                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    606         }
    607 
    608         if (folderName == null || folderName.trim().isEmpty()) {
    609             if(backup == false)
    610                 mCurrentFolder = mCurrentFolder.getRoot();
    611         }
    612         else {
    613             folder = mCurrentFolder.getSubFolder(folderName);
    614             if(folder != null)
    615                 mCurrentFolder = folder;
    616             else
    617                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    618         }
    619         if (V) Log.d(TAG, "Current Folder: " + mCurrentFolder.getName());
    620         return ResponseCodes.OBEX_HTTP_OK;
    621     }
    622 
    623     @Override
    624     public void onClose() {
    625         if (mCallback != null) {
    626             Message msg = Message.obtain(mCallback);
    627             msg.what = BluetoothMapService.MSG_SERVERSESSION_CLOSE;
    628             msg.arg1 = mMasId;
    629             msg.sendToTarget();
    630             if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out.");
    631 
    632         }
    633         if(mProviderClient != null){
    634             mProviderClient.release();
    635             mProviderClient = null;
    636         }
    637 
    638     }
    639 
    640     @Override
    641     public int onGet(Operation op) {
    642         notifyUpdateWakeLock();
    643         mIsAborted = false;
    644         HeaderSet request;
    645         String type;
    646         String name;
    647         byte[] appParamRaw = null;
    648         BluetoothMapAppParams appParams = null;
    649         try {
    650             request = op.getReceivedHeader();
    651             type = (String)request.getHeader(HeaderSet.TYPE);
    652             name = (String)request.getHeader(HeaderSet.NAME);
    653             appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
    654             if(appParamRaw != null)
    655                 appParams = new BluetoothMapAppParams(appParamRaw);
    656 
    657             if (V) logHeader(request);
    658             if (D) Log.d(TAG, "OnGet type is " + type + " name is " + name);
    659 
    660             if (type == null) {
    661                 if (V) Log.d(TAG, "type is null?" + type);
    662                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    663             }
    664 
    665             if (type.equals(TYPE_GET_FOLDER_LISTING)) {
    666                 if (V && appParams != null) {
    667                     Log.d(TAG,"TYPE_GET_FOLDER_LISTING: MaxListCount = " + appParams.getMaxListCount() +
    668                               ", ListStartOffset = " + appParams.getStartOffset());
    669                 }
    670                 return sendFolderListingRsp(op, appParams); // Block until all packets have been send.
    671             } else if (type.equals(TYPE_GET_MESSAGE_LISTING)){
    672                 if (V && appParams != null) {
    673                     Log.d(TAG,"TYPE_GET_MESSAGE_LISTING: MaxListCount = " + appParams.getMaxListCount() +
    674                               ", ListStartOffset = " + appParams.getStartOffset());
    675                     Log.d(TAG,"SubjectLength = " + appParams.getSubjectLength() + ", ParameterMask = " +
    676                               appParams.getParameterMask());
    677                     Log.d(TAG,"FilterMessageType = " + appParams.getFilterMessageType() +
    678                               ", FilterPeriodBegin = " + appParams.getFilterPeriodBegin());
    679                     Log.d(TAG,"FilterPeriodEnd = " + appParams.getFilterPeriodBegin() +
    680                               ", FilterReadStatus = " + appParams.getFilterReadStatus());
    681                     Log.d(TAG,"FilterRecipient = " + appParams.getFilterRecipient() +
    682                               ", FilterOriginator = " + appParams.getFilterOriginator());
    683                     Log.d(TAG,"FilterPriority = " + appParams.getFilterPriority());
    684                 }
    685                 return sendMessageListingRsp(op, appParams, name); // Block until all packets have been send.
    686             } else if (type.equals(TYPE_MESSAGE)){
    687                 if(V && appParams != null) {
    688                     Log.d(TAG,"TYPE_MESSAGE (GET): Attachment = " + appParams.getAttachment() +
    689                             ", Charset = " + appParams.getCharset() +
    690                             ", FractionRequest = " + appParams.getFractionRequest());
    691                 }
    692                 return sendGetMessageRsp(op, name, appParams); // Block until all packets have been send.
    693             } else {
    694                 Log.w(TAG, "unknown type request: " + type);
    695                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    696             }
    697 
    698         } catch (IllegalArgumentException e) {
    699             Log.e(TAG, "Exception:", e);
    700             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    701         } catch (ParseException e) {
    702             Log.e(TAG, "Exception:", e);
    703             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    704         } catch (Exception e) {
    705             if(D) {
    706                 Log.e(TAG, "Exception occured while handling request",e);
    707             } else {
    708                 Log.e(TAG, "Exception occured while handling request");
    709             }
    710             if(mIsAborted == true) {
    711                 if(D) Log.d(TAG, "onGet Operation Aborted");
    712                 return ResponseCodes.OBEX_HTTP_OK;
    713             } else {
    714                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    715             }
    716         }
    717     }
    718 
    719     /**
    720      * Generate and send the message listing response based on an application
    721      * parameter header. This function call will block until complete or aborted
    722      * by the peer. Fragmentation of packets larger than the obex packet size
    723      * will be handled by this function.
    724      *
    725      * @param op
    726      *            The OBEX operation.
    727      * @param appParams
    728      *            The application parameter header
    729      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
    730      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
    731      */
    732     private int sendMessageListingRsp(Operation op, BluetoothMapAppParams appParams, String folderName){
    733         OutputStream outStream = null;
    734         byte[] outBytes = null;
    735         int maxChunkSize, bytesToWrite, bytesWritten = 0, listSize;
    736         boolean hasUnread = false;
    737         HeaderSet replyHeaders = new HeaderSet();
    738         BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
    739         BluetoothMapMessageListing outList;
    740         if(appParams == null){
    741             appParams = new BluetoothMapAppParams();
    742             appParams.setMaxListCount(1024);
    743             appParams.setStartOffset(0);
    744         }
    745 
    746         BluetoothMapFolderElement folderToList = getFolderElementFromName(folderName);
    747         if(folderToList == null) {
    748             Log.w(TAG,"sendMessageListingRsp: folderToList == null - sending OBEX_HTTP_BAD_REQUEST");
    749             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    750         }
    751 
    752         // Check to see if we only need to send the size - hence no need to encode.
    753         try {
    754             // Open the OBEX body stream
    755             outStream = op.openOutputStream();
    756 
    757             if(appParams.getMaxListCount() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
    758                 appParams.setMaxListCount(1024);
    759 
    760             if(appParams.getStartOffset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
    761                 appParams.setStartOffset(0);
    762 
    763             if(appParams.getMaxListCount() != 0) {
    764                 outList = mOutContent.msgListing(folderToList, appParams);
    765                 // Generate the byte stream
    766                 outAppParams.setMessageListingSize(outList.getCount());
    767                 outBytes = outList.encode(mThreadIdSupport); // Include thread ID for clients that supports it.
    768                 hasUnread = outList.hasUnread();
    769             }
    770             else {
    771                 listSize = mOutContent.msgListingSize(folderToList, appParams);
    772                 hasUnread = mOutContent.msgListingHasUnread(folderToList, appParams);
    773                 outAppParams.setMessageListingSize(listSize);
    774                 op.noBodyHeader();
    775             }
    776 
    777             // Build the application parameter header
    778 
    779             // let the peer know if there are unread messages in the list
    780             if(hasUnread) {
    781                 outAppParams.setNewMessage(1);
    782             }else{
    783                 outAppParams.setNewMessage(0);
    784             }
    785 
    786             outAppParams.setMseTime(Calendar.getInstance().getTime().getTime());
    787             replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
    788             op.sendHeaders(replyHeaders);
    789 
    790         } catch (IOException e) {
    791             Log.w(TAG,"sendMessageListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
    792             if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
    793             if(mIsAborted == true) {
    794                 if(D) Log.d(TAG, "sendMessageListingRsp Operation Aborted");
    795                 return ResponseCodes.OBEX_HTTP_OK;
    796             } else {
    797                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    798             }
    799         } catch (IllegalArgumentException e) {
    800             Log.w(TAG,"sendMessageListingRsp: IllegalArgumentException - sending OBEX_HTTP_BAD_REQUEST", e);
    801             if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
    802             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    803         }
    804 
    805         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
    806         if(outBytes != null) {
    807             try {
    808                 while (bytesWritten < outBytes.length && mIsAborted == false) {
    809                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
    810                     outStream.write(outBytes, bytesWritten, bytesToWrite);
    811                     bytesWritten += bytesToWrite;
    812                 }
    813             } catch (IOException e) {
    814                 if(D) Log.w(TAG,e);
    815                 // We were probably aborted or disconnected
    816             } finally {
    817                 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
    818             }
    819             if(bytesWritten != outBytes.length && !mIsAborted) {
    820                 Log.w(TAG,"sendMessageListingRsp: bytesWritten != outBytes.length - sending OBEX_HTTP_BAD_REQUEST");
    821                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    822             }
    823         } else {
    824             if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
    825         }
    826         return ResponseCodes.OBEX_HTTP_OK;
    827     }
    828 
    829     /**
    830      * Generate and send the Folder listing response based on an application
    831      * parameter header. This function call will block until complete or aborted
    832      * by the peer. Fragmentation of packets larger than the obex packet size
    833      * will be handled by this function.
    834      *
    835      * @param op
    836      *            The OBEX operation.
    837      * @param appParams
    838      *            The application parameter header
    839      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
    840      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
    841      */
    842     private int sendFolderListingRsp(Operation op, BluetoothMapAppParams appParams){
    843         OutputStream outStream = null;
    844         byte[] outBytes = null;
    845         BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
    846         int maxChunkSize, bytesWritten = 0;
    847         HeaderSet replyHeaders = new HeaderSet();
    848         int bytesToWrite, maxListCount, listStartOffset;
    849         if(appParams == null){
    850             appParams = new BluetoothMapAppParams();
    851             appParams.setMaxListCount(1024);
    852         }
    853 
    854         if(V)
    855             Log.v(TAG,"sendFolderList for " + mCurrentFolder.getName());
    856 
    857         try {
    858             maxListCount = appParams.getMaxListCount();
    859             listStartOffset = appParams.getStartOffset();
    860 
    861             if(listStartOffset == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
    862                 listStartOffset = 0;
    863 
    864             if(maxListCount == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
    865                 maxListCount = 1024;
    866 
    867             if(maxListCount != 0)
    868             {
    869                 outBytes = mCurrentFolder.encode(listStartOffset, maxListCount);
    870                 outStream = op.openOutputStream();
    871             }
    872 
    873             // Build and set the application parameter header
    874             outAppParams.setFolderListingSize(mCurrentFolder.getSubFolderCount());
    875             replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
    876             op.sendHeaders(replyHeaders);
    877 
    878         } catch (IOException e1) {
    879             Log.w(TAG,"sendFolderListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
    880             if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
    881             if(mIsAborted == true) {
    882                 if(D) Log.d(TAG, "sendFolderListingRsp Operation Aborted");
    883                 return ResponseCodes.OBEX_HTTP_OK;
    884             } else {
    885                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    886             }
    887         } catch (IllegalArgumentException e1) {
    888             Log.w(TAG,"sendFolderListingRsp: IllegalArgumentException - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
    889             if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
    890             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    891         }
    892 
    893         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
    894 
    895         if(outBytes != null) {
    896             try {
    897                 while (bytesWritten < outBytes.length && mIsAborted == false) {
    898                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
    899                     outStream.write(outBytes, bytesWritten, bytesToWrite);
    900                     bytesWritten += bytesToWrite;
    901                 }
    902             } catch (IOException e) {
    903                 // We were probably aborted or disconnected
    904             } finally {
    905                 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
    906             }
    907             if(V)
    908                 Log.v(TAG,"sendFolderList sent " + bytesWritten + " bytes out of "+ outBytes.length);
    909             if(bytesWritten == outBytes.length || mIsAborted)
    910                 return ResponseCodes.OBEX_HTTP_OK;
    911             else
    912                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    913         }
    914 
    915         return ResponseCodes.OBEX_HTTP_OK;
    916     }
    917 
    918     /**
    919      * Generate and send the get message response based on an application
    920      * parameter header and a handle.
    921      *
    922      * @param op
    923      *            The OBEX operation.
    924      * @param appParams
    925      *            The application parameter header
    926      * @param handle
    927      *            The handle of the requested message
    928      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
    929      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
    930      */
    931     private int sendGetMessageRsp(Operation op, String handle, BluetoothMapAppParams appParams){
    932         OutputStream outStream = null;
    933         byte[] outBytes = null;
    934         int maxChunkSize, bytesToWrite, bytesWritten = 0;
    935 
    936         try {
    937             outBytes = mOutContent.getMessage(handle, appParams, mCurrentFolder);
    938             outStream = op.openOutputStream();
    939 
    940             // If it is a fraction request of Email message, set header before responding
    941             if ((BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.EMAIL)) &&
    942                     (appParams.getFractionRequest() ==
    943                     BluetoothMapAppParams.FRACTION_REQUEST_FIRST)) {
    944                 BluetoothMapAppParams outAppParams  = new BluetoothMapAppParams();;
    945                 HeaderSet replyHeaders = new HeaderSet();
    946                 outAppParams.setFractionDeliver(BluetoothMapAppParams.FRACTION_DELIVER_LAST);
    947                 // Build and set the application parameter header
    948                 replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER,
    949                         outAppParams.EncodeParams());
    950                 op.sendHeaders(replyHeaders);
    951                 if(V) Log.v(TAG,"sendGetMessageRsp fractionRequest - " +
    952                         "set FRACTION_DELIVER_LAST header");
    953             }
    954 
    955         } catch (IOException e) {
    956             Log.w(TAG,"sendGetMessageRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
    957             if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
    958             if(mIsAborted == true) {
    959                 if(D) Log.d(TAG, "sendGetMessageRsp Operation Aborted");
    960                 return ResponseCodes.OBEX_HTTP_OK;
    961             } else {
    962                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    963             }
    964         } catch (IllegalArgumentException e) {
    965             Log.w(TAG,"sendGetMessageRsp: IllegalArgumentException (e.g. invalid handle) - " +
    966                     "sending OBEX_HTTP_BAD_REQUEST", e);
    967             if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
    968             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    969         }
    970 
    971         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
    972 
    973         if(outBytes != null) {
    974             try {
    975                 while (bytesWritten < outBytes.length && mIsAborted == false) {
    976                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
    977                     outStream.write(outBytes, bytesWritten, bytesToWrite);
    978                     bytesWritten += bytesToWrite;
    979                 }
    980             } catch (IOException e) {
    981                 // We were probably aborted or disconnected
    982                 if(D && e.getMessage().equals("Abort Received")) {
    983                     Log.w(TAG, "getMessage() Aborted...", e);
    984                 }
    985             } finally {
    986                 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
    987             }
    988             if(bytesWritten == outBytes.length || mIsAborted)
    989                 return ResponseCodes.OBEX_HTTP_OK;
    990             else
    991                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    992         }
    993 
    994         return ResponseCodes.OBEX_HTTP_OK;
    995     }
    996 
    997     private void notifyUpdateWakeLock() {
    998         if(mCallback != null) {
    999             Message msg = Message.obtain(mCallback);
   1000             msg.what = BluetoothMapService.MSG_ACQUIRE_WAKE_LOCK;
   1001             msg.sendToTarget();
   1002         }
   1003     }
   1004 
   1005     private static final void logHeader(HeaderSet hs) {
   1006         Log.v(TAG, "Dumping HeaderSet " + hs.toString());
   1007         try {
   1008             Log.v(TAG, "CONNECTION_ID : " + hs.getHeader(HeaderSet.CONNECTION_ID));
   1009             Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME));
   1010             Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE));
   1011             Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET));
   1012             Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO));
   1013             Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER));
   1014         } catch (IOException e) {
   1015             Log.e(TAG, "dump HeaderSet error " + e);
   1016         }
   1017         Log.v(TAG, "NEW!!! Dumping HeaderSet END");
   1018     }
   1019 }
   1020