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