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