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