BluetoothMapObexServer.java revision 2aeba34a2dbb09d509a982dedd1689164317886a
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*/
15package com.android.bluetooth.map;
16
17import android.content.ContentProviderClient;
18import android.content.ContentResolver;
19import android.content.Context;
20import android.database.Cursor;
21import android.net.Uri;
22import android.os.Bundle;
23import android.os.Handler;
24import android.os.Message;
25import android.os.ParcelUuid;
26import android.os.RemoteException;
27import android.os.UserManager;
28import android.telephony.TelephonyManager;
29import android.text.format.DateUtils;
30import android.util.Log;
31
32import com.android.bluetooth.SignedLongLong;
33import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
34import com.android.bluetooth.mapapi.BluetoothMapContract;
35
36import java.io.IOException;
37import java.io.InputStream;
38import java.io.OutputStream;
39import java.text.ParseException;
40import java.util.Arrays;
41import java.util.Calendar;
42
43import javax.obex.HeaderSet;
44import javax.obex.Operation;
45import javax.obex.ResponseCodes;
46import javax.obex.ServerRequestHandler;
47
48
49public 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