• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2 * Copyright (C) 2014 Samsung System LSI
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *      http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 package com.android.bluetooth.map;
16 
17 import android.content.ContentProviderClient;
18 import android.content.ContentResolver;
19 import android.content.Context;
20 import android.database.Cursor;
21 import android.net.Uri;
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.os.Message;
25 import android.os.RemoteException;
26 import com.android.bluetooth.mapapi.BluetoothMapContract;
27 import android.text.format.DateUtils;
28 import android.util.Log;
29 
30 import com.android.bluetooth.map.BluetoothMapUtils;
31 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
32 
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.io.OutputStream;
36 import java.text.ParseException;
37 import java.util.Arrays;
38 import java.util.Calendar;
39 
40 import javax.obex.HeaderSet;
41 import javax.obex.Operation;
42 import javax.obex.ResponseCodes;
43 import javax.obex.ServerRequestHandler;
44 
45 
46 public class BluetoothMapObexServer extends ServerRequestHandler {
47 
48     private static final String TAG = "BluetoothMapObexServer";
49 
50     private static final boolean D = BluetoothMapService.DEBUG;
51     private static final boolean V = BluetoothMapService.VERBOSE;
52 
53     private static final int UUID_LENGTH = 16;
54 
55     private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS;
56 
57     /* OBEX header and value used to detect clients that support threadId in the message listing. */
58     private static final int THREADED_MAIL_HEADER_ID = 0xFA;
59     private static final long THREAD_MAIL_KEY = 0x534c5349;
60 
61     // 128 bit UUID for MAP
62     private static final byte[] MAP_TARGET = new byte[] {
63              (byte)0xBB, (byte)0x58, (byte)0x2B, (byte)0x40,
64              (byte)0x42, (byte)0x0C, (byte)0x11, (byte)0xDB,
65              (byte)0xB0, (byte)0xDE, (byte)0x08, (byte)0x00,
66              (byte)0x20, (byte)0x0C, (byte)0x9A, (byte)0x66
67              };
68 
69     /* Message types */
70     private static final String TYPE_GET_FOLDER_LISTING  = "x-obex/folder-listing";
71     private static final String TYPE_GET_MESSAGE_LISTING = "x-bt/MAP-msg-listing";
72     private static final String TYPE_MESSAGE             = "x-bt/message";
73     private static final String TYPE_SET_MESSAGE_STATUS  = "x-bt/messageStatus";
74     private static final String TYPE_SET_NOTIFICATION_REGISTRATION = "x-bt/MAP-NotificationRegistration";
75     private static final String TYPE_MESSAGE_UPDATE      = "x-bt/MAP-messageUpdate";
76 
77     private BluetoothMapFolderElement mCurrentFolder;
78 
79     private BluetoothMapContentObserver mObserver = null;
80 
81     private Handler mCallback = null;
82 
83     private Context mContext;
84 
85     private boolean mIsAborted = false;
86 
87     BluetoothMapContent mOutContent;
88 
89     private String mBaseEmailUriString = null;
90     private long mAccountId = 0;
91     private BluetoothMapEmailSettingsItem mAccount = null;
92     private Uri mEmailFolderUri = null;
93 
94     private int mMasId = 0;
95 
96     private boolean mEnableSmsMms = false;
97     private boolean mThreadIdSupport = false; // true if peer supports threadId in msg listing
98     private String mAuthority;
99     private ContentResolver mResolver;
100     private ContentProviderClient mProviderClient = null;
101 
BluetoothMapObexServer(Handler callback, Context context, BluetoothMapContentObserver observer, int masId, BluetoothMapEmailSettingsItem account, boolean enableSmsMms)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      */
acquireUnstableContentProviderOrThrow()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      */
buildFolderStructure()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      */
addBaseFolders(BluetoothMapFolderElement root)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      */
addSmsMmsFolders(BluetoothMapFolderElement root)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      */
addEmailFolders(BluetoothMapFolderElement parentFolder)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
onConnect(final HeaderSet request, HeaderSet reply)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
onDisconnect(final HeaderSet req, final HeaderSet resp)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
onAbort(HeaderSet request, HeaderSet reply)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
onPut(final Operation op)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 
updateInbox()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 
getFolderElementFromName(String folderName)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 
pushMessage(final Operation op, String folderName, BluetoothMapAppParams appParams)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 
setMessageStatus(String msgHandle, BluetoothMapAppParams appParams)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
onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup, final boolean create)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
onClose()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
onGet(Operation op)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      */
sendMessageListingRsp(Operation op, BluetoothMapAppParams appParams, String folderName)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      */
sendFolderListingRsp(Operation op, BluetoothMapAppParams appParams)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      */
sendGetMessageRsp(Operation op, String handle, BluetoothMapAppParams appParams)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 
notifyUpdateWakeLock()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 
logHeader(HeaderSet hs)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