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