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