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