• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
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
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       //          hasUnread = outList.hasUnread();
1213                 if(D) Log.d(TAG, "outBytes size:"+ outBytes.length);
1214             } else {
1215                 outList = mOutContent.convoListing(appParams, true);
1216                 outAppParams.setConvoListingSize(outList.getCount());
1217                 if(mEnableSmsMms) {
1218                     mOutContent.refreshSmsMmsConvoVersions();
1219                 }
1220                 if(mAccount != null) {
1221                     mOutContent.refreshImEmailConvoVersions();
1222                 }
1223                 // Force update of version counter if needed
1224                 mObserver.refreshConvoListVersionCounter();
1225                 if(0 < (mRemoteFeatureMask &
1226                         BluetoothMapUtils.MAP_FEATURE_CONVERSATION_VERSION_COUNTER_BIT)) {
1227                     outAppParams.setConvoListingVerCounter(
1228                             mMasInstance.getCombinedConvoListVersionCounter(), 0);
1229                 }
1230                 op.noBodyHeader();
1231             }
1232             if(D) Log.d(TAG, "outList size:"+ outList.getCount()
1233                     + " MaxListCount: "+appParams.getMaxListCount());
1234             outList = null; // We don't need it anymore - we might as well give it up for GC
1235             outAppParams.setDatabaseIdentifier(0, mMasInstance.getDbIdentifier());
1236 
1237             // Build the application parameter header
1238             // The MseTime is not in the CR - but I think it is missing.
1239             outAppParams.setMseTime(Calendar.getInstance().getTime().getTime());
1240             replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
1241             op.sendHeaders(replyHeaders);
1242 
1243         } catch (IOException e) {
1244             Log.w(TAG,"sendConvoListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
1245             if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
1246             if(mIsAborted == true) {
1247                 if(D) Log.d(TAG, "sendConvoListingRsp Operation Aborted");
1248                 return ResponseCodes.OBEX_HTTP_OK;
1249             } else {
1250                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
1251             }
1252         } catch (IllegalArgumentException e) {
1253             Log.w(TAG,"sendConvoListingRsp: IllegalArgumentException" +
1254                     " - sending OBEX_HTTP_BAD_REQUEST", e);
1255             if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
1256             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
1257         }
1258 
1259         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
1260         if(outBytes != null) {
1261             try {
1262                 while (bytesWritten < outBytes.length && mIsAborted == false) {
1263                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
1264                     outStream.write(outBytes, bytesWritten, bytesToWrite);
1265                     bytesWritten += bytesToWrite;
1266                 }
1267             } catch (IOException e) {
1268                 if(D) Log.w(TAG,e);
1269                 // We were probably aborted or disconnected
1270             } finally {
1271                 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
1272             }
1273             if(bytesWritten != outBytes.length && !mIsAborted) {
1274                 Log.w(TAG,"sendConvoListingRsp: bytesWritten != outBytes.length" +
1275                         " - sending OBEX_HTTP_BAD_REQUEST");
1276                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
1277             }
1278         } else {
1279             if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
1280         }
1281         return ResponseCodes.OBEX_HTTP_OK;
1282     }
1283 
1284     /**
1285      * Generate and send the Folder listing response based on an application
1286      * parameter header. This function call will block until complete or aborted
1287      * by the peer. Fragmentation of packets larger than the obex packet size
1288      * will be handled by this function.
1289      *
1290      * @param op
1291      *            The OBEX operation.
1292      * @param appParams
1293      *            The application parameter header
1294      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
1295      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
1296      */
sendFolderListingRsp(Operation op, BluetoothMapAppParams appParams)1297     private int sendFolderListingRsp(Operation op, BluetoothMapAppParams appParams){
1298         OutputStream outStream = null;
1299         byte[] outBytes = null;
1300         BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
1301         int maxChunkSize, bytesWritten = 0;
1302         HeaderSet replyHeaders = new HeaderSet();
1303         int bytesToWrite, maxListCount, listStartOffset;
1304         if(appParams == null){
1305             appParams = new BluetoothMapAppParams();
1306             appParams.setMaxListCount(1024);
1307         }
1308 
1309         if(V)
1310             Log.v(TAG,"sendFolderList for " + mCurrentFolder.getName());
1311 
1312         try {
1313             maxListCount = appParams.getMaxListCount();
1314             listStartOffset = appParams.getStartOffset();
1315 
1316             if(listStartOffset == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
1317                 listStartOffset = 0;
1318 
1319             if(maxListCount == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
1320                 maxListCount = 1024;
1321 
1322             if(maxListCount != 0)
1323             {
1324                 outBytes = mCurrentFolder.encode(listStartOffset, maxListCount);
1325                 outStream = op.openOutputStream();
1326             } else {
1327                 // ESR08 specified that this shall only be included for MaxListCount=0
1328                 outAppParams.setFolderListingSize(mCurrentFolder.getSubFolderCount());
1329                 op.noBodyHeader();
1330             }
1331 
1332             // Build and set the application parameter header
1333             replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
1334             op.sendHeaders(replyHeaders);
1335 
1336         } catch (IOException e1) {
1337             Log.w(TAG,"sendFolderListingRsp: IOException" +
1338                     " - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
1339             if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
1340             if(mIsAborted == true) {
1341                 if(D) Log.d(TAG, "sendFolderListingRsp Operation Aborted");
1342                 return ResponseCodes.OBEX_HTTP_OK;
1343             } else {
1344                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
1345             }
1346         } catch (IllegalArgumentException e1) {
1347             Log.w(TAG,"sendFolderListingRsp: IllegalArgumentException" +
1348                     " - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
1349             if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
1350             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
1351         }
1352 
1353         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
1354 
1355         if(outBytes != null) {
1356             try {
1357                 while (bytesWritten < outBytes.length && mIsAborted == false) {
1358                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
1359                     outStream.write(outBytes, bytesWritten, bytesToWrite);
1360                     bytesWritten += bytesToWrite;
1361                 }
1362             } catch (IOException e) {
1363                 // We were probably aborted or disconnected
1364             } finally {
1365                 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
1366             }
1367             if(V)
1368                 Log.v(TAG,"sendFolderList sent " + bytesWritten+" bytes out of "+ outBytes.length);
1369             if(bytesWritten == outBytes.length || mIsAborted)
1370                 return ResponseCodes.OBEX_HTTP_OK;
1371             else
1372                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
1373         }
1374 
1375         return ResponseCodes.OBEX_HTTP_OK;
1376     }
1377 
1378     /**
1379      * Generate and send the get MAS Instance Information response based on an MAS Instance
1380      *
1381      * @param op
1382      *            The OBEX operation.
1383      * @param appParams
1384      *            The application parameter header
1385      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
1386      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
1387      */
sendMASInstanceInformationRsp(Operation op, BluetoothMapAppParams appParams)1388     private int sendMASInstanceInformationRsp(Operation op, BluetoothMapAppParams appParams){
1389 
1390         OutputStream outStream = null;
1391         byte[] outBytes = null;
1392         String outString = null;
1393         int maxChunkSize, bytesToWrite, bytesWritten = 0;
1394 
1395         try {
1396             if(mMasId == appParams.getMasInstanceId()) {
1397                 if(mAccount != null) {
1398                     if(mAccount.getType() == TYPE.EMAIL) {
1399                         outString = (mAccount.getName() != null) ? mAccount.getName() :
1400                             BluetoothMapMasInstance.TYPE_EMAIL_STR;
1401                     } else if(mAccount.getType() == TYPE.IM){
1402                         outString = mAccount.getUciFull();
1403                         if(outString == null) {
1404                             String uci = mAccount.getUci();
1405                             // TODO: Do we need to align this with HF/PBAP
1406                             StringBuilder sb =
1407                                     new StringBuilder(uci == null ? 5 : 5 + uci.length());
1408                             sb.append("un");
1409                             if(mMasId < 10) {
1410                                 sb.append("00");
1411                             } else if(mMasId < 100) {
1412                                 sb.append("0");
1413                             }
1414                             sb.append(mMasId);
1415                             if(uci != null) {
1416                                 sb.append(":").append(uci);
1417                             }
1418                             outString = sb.toString();
1419                         }
1420                     }
1421                 } else {
1422                     outString = BluetoothMapMasInstance.TYPE_SMS_MMS_STR;
1423                     // TODO: Add phone number if possible
1424                 }
1425             } else {
1426                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
1427             }
1428 
1429             /* Ensure byte array max length is 200 containing valid UTF-8 characters */
1430             outBytes = BluetoothMapUtils.truncateUtf8StringToBytearray(outString,
1431                     MAS_INSTANCE_INFORMATION_LENGTH);
1432 
1433             // Open the OBEX body stream
1434             outStream = op.openOutputStream();
1435 
1436         } catch (IOException e) {
1437             Log.w(TAG,"sendMASInstanceInformationRsp: IOException" +
1438                     " - sending OBEX_HTTP_BAD_REQUEST", e);
1439             if(mIsAborted == true) {
1440                 if(D) Log.d(TAG, "sendMASInstanceInformationRsp Operation Aborted");
1441                 return ResponseCodes.OBEX_HTTP_OK;
1442             } else {
1443                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
1444             }
1445         }
1446 
1447         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
1448 
1449         if(outBytes != null) {
1450             try {
1451                 while (bytesWritten < outBytes.length && mIsAborted == false) {
1452                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
1453                     outStream.write(outBytes, bytesWritten, bytesToWrite);
1454                     bytesWritten += bytesToWrite;
1455                 }
1456             } catch (IOException e) {
1457                 // We were probably aborted or disconnected
1458             } finally {
1459                 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
1460             }
1461             if(V)
1462                 Log.v(TAG,"sendMASInstanceInformationRsp sent " + bytesWritten +
1463                         " bytes out of "+ outBytes.length);
1464             if(bytesWritten == outBytes.length || mIsAborted)
1465                 return ResponseCodes.OBEX_HTTP_OK;
1466             else
1467                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
1468         }
1469         return ResponseCodes.OBEX_HTTP_OK;
1470     }
1471 
1472     /**
1473      * Generate and send the get message response based on an application
1474      * parameter header and a handle.
1475      *
1476      * @param op
1477      *            The OBEX operation.
1478      * @param handle
1479      *            The handle of the requested message
1480      * @param appParams
1481      *            The application parameter header
1482      * @param version
1483      *              The string representation of the version number(i.e. "1.0")
1484      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
1485      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
1486      */
sendGetMessageRsp(Operation op, String handle, BluetoothMapAppParams appParams, String version)1487     private int sendGetMessageRsp(Operation op, String handle,
1488             BluetoothMapAppParams appParams, String version){
1489         OutputStream outStream = null;
1490         byte[] outBytes = null;
1491         int maxChunkSize, bytesToWrite, bytesWritten = 0;
1492 
1493         try {
1494             outBytes = mOutContent.getMessage(handle, appParams, mCurrentFolder, version);
1495             outStream = op.openOutputStream();
1496 
1497             // If it is a fraction request of Email message, set header before responding
1498             if ((BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.EMAIL)||
1499                     (BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.IM))) &&
1500                     (appParams.getFractionRequest() ==
1501                     BluetoothMapAppParams.FRACTION_REQUEST_FIRST)) {
1502                 BluetoothMapAppParams outAppParams  = new BluetoothMapAppParams();
1503                 HeaderSet replyHeaders = new HeaderSet();
1504                 outAppParams.setFractionDeliver(BluetoothMapAppParams.FRACTION_DELIVER_LAST);
1505                 // Build and set the application parameter header
1506                 replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER,
1507                         outAppParams.EncodeParams());
1508                 op.sendHeaders(replyHeaders);
1509                 if(V) Log.v(TAG,"sendGetMessageRsp fractionRequest - " +
1510                         "set FRACTION_DELIVER_LAST header");
1511             }
1512 
1513         } catch (IOException e) {
1514             Log.w(TAG,"sendGetMessageRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
1515             if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
1516             if(mIsAborted == true) {
1517                 if(D) Log.d(TAG, "sendGetMessageRsp Operation Aborted");
1518                 return ResponseCodes.OBEX_HTTP_OK;
1519             } else {
1520                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
1521             }
1522         } catch (IllegalArgumentException e) {
1523             Log.w(TAG,"sendGetMessageRsp: IllegalArgumentException (e.g. invalid handle) - " +
1524                     "sending OBEX_HTTP_BAD_REQUEST", e);
1525             if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
1526             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
1527         }
1528 
1529         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
1530 
1531         if(outBytes != null) {
1532             try {
1533                 while (bytesWritten < outBytes.length && mIsAborted == false) {
1534                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
1535                     outStream.write(outBytes, bytesWritten, bytesToWrite);
1536                     bytesWritten += bytesToWrite;
1537                 }
1538             } catch (IOException e) {
1539                 // We were probably aborted or disconnected
1540                 if(D && e.getMessage().equals("Abort Received")) {
1541                     Log.w(TAG, "getMessage() Aborted...", e);
1542                 }
1543             } finally {
1544                 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
1545             }
1546             if(bytesWritten == outBytes.length || mIsAborted)
1547                 return ResponseCodes.OBEX_HTTP_OK;
1548             else
1549                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
1550         }
1551 
1552         return ResponseCodes.OBEX_HTTP_OK;
1553     }
1554 
1555     @Override
onDelete(HeaderSet request, HeaderSet reply)1556     public int onDelete(HeaderSet request, HeaderSet reply) {
1557         if(D) Log.v(TAG, "onDelete() " + request.toString());
1558         mIsAborted = false;
1559         notifyUpdateWakeLock();
1560         String type, name;
1561         byte[] appParamRaw;
1562         BluetoothMapAppParams appParams = null;
1563 
1564         /* TODO: If this is to be placed here, we need to cleanup - e.g. the exception handling */
1565         try {
1566             type = (String)request.getHeader(HeaderSet.TYPE);
1567 
1568             name = (String)request.getHeader(HeaderSet.NAME);
1569             appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
1570             if(appParamRaw != null)
1571                 appParams = new BluetoothMapAppParams(appParamRaw);
1572             if(D) Log.d(TAG,"type = " + type + ", name = " + name);
1573             if(type.equals(TYPE_SET_NOTIFICATION_FILTER)) {
1574                 if(V) {
1575                     Log.d(TAG,"TYPE_SET_NOTIFICATION_FILTER: NotificationFilter: "
1576                             + appParams.getNotificationFilter());
1577                 }
1578                 mObserver.setNotificationFilter(appParams.getNotificationFilter());
1579                 return ResponseCodes.OBEX_HTTP_OK;
1580             }  else if (type.equals(TYPE_SET_OWNER_STATUS)) {
1581                 if(V) {
1582                     Log.d(TAG,"TYPE_SET_OWNER_STATUS:" +
1583                           " PresenceAvailability " + appParams.getPresenceAvailability() +
1584                           ", PresenceStatus: " + appParams.getPresenceStatus() +
1585                           ", LastActivity: " + appParams.getLastActivityString() +
1586                           ", ChatStatus: " + appParams.getChatState() +
1587                           ", ChatStatusConvoId: " + appParams.getChatStateConvoIdString());
1588                 }
1589                 return setOwnerStatus(name, appParams);
1590             }
1591 
1592         } catch (RemoteException e){
1593             //reload the providerClient and return error
1594             try {
1595                 mProviderClient = acquireUnstableContentProviderOrThrow();
1596             }catch (RemoteException e2){
1597                 //should not happen
1598             }
1599             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
1600         }catch (Exception e) {
1601 
1602             if(D) {
1603                 Log.e(TAG, "Exception occured while handling request",e);
1604             } else {
1605                 Log.e(TAG, "Exception occured while handling request");
1606             }
1607             if(mIsAborted) {
1608                 return ResponseCodes.OBEX_HTTP_OK;
1609             } else {
1610                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
1611             }
1612         }
1613         return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
1614     }
1615 
notifyUpdateWakeLock()1616     private void notifyUpdateWakeLock() {
1617         if(mCallback != null) {
1618             Message msg = Message.obtain(mCallback);
1619             msg.what = BluetoothMapService.MSG_ACQUIRE_WAKE_LOCK;
1620             msg.sendToTarget();
1621         }
1622     }
1623 
logHeader(HeaderSet hs)1624     private static final void logHeader(HeaderSet hs) {
1625         Log.v(TAG, "Dumping HeaderSet " + hs.toString());
1626         try {
1627             Log.v(TAG, "CONNECTION_ID : " + hs.getHeader(HeaderSet.CONNECTION_ID));
1628             Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME));
1629             Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE));
1630             Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET));
1631             Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO));
1632             Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER));
1633         } catch (IOException e) {
1634             Log.e(TAG, "dump HeaderSet error " + e);
1635         }
1636         Log.v(TAG, "NEW!!! Dumping HeaderSet END");
1637     }
1638 }
1639