BluetoothPbapObexServer.java revision 0dcd2262d853c2011e11617a8efba6758370c41f
1/*
2 * Copyright (c) 2008-2009, Motorola, Inc.
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * - Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 *
12 * - Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
15 *
16 * - Neither the name of the Motorola, Inc. nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package com.android.bluetooth.pbap;
34
35import android.content.Context;
36import android.os.Message;
37import android.os.Handler;
38import android.text.TextUtils;
39import android.util.Log;
40import android.provider.CallLog.Calls;
41import android.provider.ContactsContract.Contacts;
42import android.provider.CallLog;
43
44import java.io.IOException;
45import java.io.OutputStream;
46import java.util.ArrayList;
47import java.util.Arrays;
48
49import javax.obex.ServerRequestHandler;
50import javax.obex.ResponseCodes;
51import javax.obex.ApplicationParameter;
52import javax.obex.ServerOperation;
53import javax.obex.Operation;
54import javax.obex.HeaderSet;
55
56public class BluetoothPbapObexServer extends ServerRequestHandler {
57
58    private static final String TAG = "BluetoothPbapObexServer";
59
60    private static final boolean D = BluetoothPbapService.DEBUG;
61
62    private static final boolean V = BluetoothPbapService.VERBOSE;
63
64    private static final int UUID_LENGTH = 16;
65
66    // The length of suffix of vcard name - ".vcf" is 5
67    private static final int VCARD_NAME_SUFFIX_LENGTH = 5;
68
69    // 128 bit UUID for PBAP
70    private static final byte[] PBAP_TARGET = new byte[] {
71            0x79, 0x61, 0x35, (byte)0xf0, (byte)0xf0, (byte)0xc5, 0x11, (byte)0xd8, 0x09, 0x66,
72            0x08, 0x00, 0x20, 0x0c, (byte)0x9a, 0x66
73    };
74
75    // Currently not support SIM card
76    private static final String[] LEGAL_PATH = {
77            "/telecom", "/telecom/pb", "/telecom/ich", "/telecom/och", "/telecom/mch",
78            "/telecom/cch"
79    };
80
81    @SuppressWarnings("unused")
82    private static final String[] LEGAL_PATH_WITH_SIM = {
83            "/telecom", "/telecom/pb", "/telecom/ich", "/telecom/och", "/telecom/mch",
84            "/telecom/cch", "/SIM1", "/SIM1/telecom", "/SIM1/telecom/ich", "/SIM1/telecom/och",
85            "/SIM1/telecom/mch", "/SIM1/telecom/cch", "/SIM1/telecom/pb"
86
87    };
88
89    // SIM card
90    private static final String SIM1 = "SIM1";
91
92    // missed call history
93    private static final String MCH = "mch";
94
95    // incoming call history
96    private static final String ICH = "ich";
97
98    // outgoing call history
99    private static final String OCH = "och";
100
101    // combined call history
102    private static final String CCH = "cch";
103
104    // phone book
105    private static final String PB = "pb";
106
107    private static final String ICH_PATH = "/telecom/ich";
108
109    private static final String OCH_PATH = "/telecom/och";
110
111    private static final String MCH_PATH = "/telecom/mch";
112
113    private static final String CCH_PATH = "/telecom/cch";
114
115    private static final String PB_PATH = "/telecom/pb";
116
117    // type for list vcard objects
118    private static final String TYPE_LISTING = "x-bt/vcard-listing";
119
120    // type for get single vcard object
121    private static final String TYPE_VCARD = "x-bt/vcard";
122
123    // to indicate if need send body besides headers
124    private static final int NEED_SEND_BODY = -1;
125
126    // type for download all vcard objects
127    private static final String TYPE_PB = "x-bt/phonebook";
128
129    // The number of indexes in the phone book.
130    private boolean mNeedPhonebookSize = false;
131
132    // The number of missed calls that have not been checked on the PSE at the
133    // point of the request. Only apply to "mch" case.
134    private boolean mNeedNewMissedCallsNum = false;
135
136    private int mMissedCallSize = 0;
137
138    // record current path the client are browsing
139    private String mCurrentPath = "";
140
141    private long mConnectionId;
142
143    private Handler mCallback = null;
144
145    private Context mContext;
146
147    private BluetoothPbapVcardManager mVcardManager;
148
149    private int mOrderBy  = ORDER_BY_INDEXED;
150
151    private static int CALLLOG_NUM_LIMIT = 50;
152
153    public static int ORDER_BY_INDEXED = 0;
154
155    public static int ORDER_BY_ALPHABETICAL = 1;
156
157    public static boolean sIsAborted = false;
158
159    public static class ContentType {
160        public static final int PHONEBOOK = 1;
161
162        public static final int INCOMING_CALL_HISTORY = 2;
163
164        public static final int OUTGOING_CALL_HISTORY = 3;
165
166        public static final int MISSED_CALL_HISTORY = 4;
167
168        public static final int COMBINED_CALL_HISTORY = 5;
169    }
170
171    public BluetoothPbapObexServer(Handler callback, Context context) {
172        super();
173        mConnectionId = -1;
174        mCallback = callback;
175        mContext = context;
176        mVcardManager = new BluetoothPbapVcardManager(mContext);
177
178        // set initial value when ObexServer created
179        mMissedCallSize = mVcardManager.getPhonebookSize(ContentType.MISSED_CALL_HISTORY);
180        if (D) Log.d(TAG, "Initialize mMissedCallSize=" + mMissedCallSize);
181    }
182
183    @Override
184    public int onConnect(final HeaderSet request, HeaderSet reply) {
185        if (V) logHeader(request);
186        try {
187            byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET);
188            if (uuid == null) {
189                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
190            }
191            if (D) Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid));
192
193            if (uuid.length != UUID_LENGTH) {
194                Log.w(TAG, "Wrong UUID length");
195                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
196            }
197            for (int i = 0; i < UUID_LENGTH; i++) {
198                if (uuid[i] != PBAP_TARGET[i]) {
199                    Log.w(TAG, "Wrong UUID");
200                    return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
201                }
202            }
203            reply.setHeader(HeaderSet.WHO, uuid);
204        } catch (IOException e) {
205            Log.e(TAG, e.toString());
206            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
207        }
208
209        try {
210            byte[] remote = (byte[])request.getHeader(HeaderSet.WHO);
211            if (remote != null) {
212                if (D) Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote));
213                reply.setHeader(HeaderSet.TARGET, remote);
214            }
215        } catch (IOException e) {
216            Log.e(TAG, e.toString());
217            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
218        }
219
220        if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " +
221                "MSG_SESSION_ESTABLISHED msg.");
222
223        Message msg = Message.obtain(mCallback);
224        msg.what = BluetoothPbapService.MSG_SESSION_ESTABLISHED;
225        msg.sendToTarget();
226
227        return ResponseCodes.OBEX_HTTP_OK;
228    }
229
230    @Override
231    public void onDisconnect(final HeaderSet req, final HeaderSet resp) {
232        if (D) Log.d(TAG, "onDisconnect(): enter");
233        if (V) logHeader(req);
234
235        resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
236        if (mCallback != null) {
237            Message msg = Message.obtain(mCallback);
238            msg.what = BluetoothPbapService.MSG_SESSION_DISCONNECTED;
239            msg.sendToTarget();
240            if (V) Log.v(TAG, "onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out.");
241        }
242    }
243
244    @Override
245    public int onAbort(HeaderSet request, HeaderSet reply) {
246        if (D) Log.d(TAG, "onAbort(): enter.");
247        sIsAborted = true;
248        return ResponseCodes.OBEX_HTTP_OK;
249    }
250
251    @Override
252    public int onPut(final Operation op) {
253        if (D) Log.d(TAG, "onPut(): not support PUT request.");
254        return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
255    }
256
257    @Override
258    public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup,
259            final boolean create) {
260        if (V) logHeader(request);
261        if (D) Log.d(TAG, "before setPath, mCurrentPath ==  " + mCurrentPath);
262
263        String current_path_tmp = mCurrentPath;
264        String tmp_path = null;
265        try {
266            tmp_path = (String)request.getHeader(HeaderSet.NAME);
267        } catch (IOException e) {
268            Log.e(TAG, "Get name header fail");
269            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
270        }
271        if (D) Log.d(TAG, "backup=" + backup + " create=" + create + " name=" + tmp_path);
272
273        if (backup) {
274            if (current_path_tmp.length() != 0) {
275                current_path_tmp = current_path_tmp.substring(0,
276                        current_path_tmp.lastIndexOf("/"));
277            }
278        } else {
279            if (tmp_path == null) {
280                current_path_tmp = "";
281            } else {
282                current_path_tmp = current_path_tmp + "/" + tmp_path;
283            }
284        }
285
286        if ((current_path_tmp.length() != 0) && (!isLegalPath(current_path_tmp))) {
287            if (create) {
288                Log.w(TAG, "path create is forbidden!");
289                return ResponseCodes.OBEX_HTTP_FORBIDDEN;
290            } else {
291                Log.w(TAG, "path is not legal");
292                return ResponseCodes.OBEX_HTTP_NOT_FOUND;
293            }
294        }
295        mCurrentPath = current_path_tmp;
296        if (V) Log.v(TAG, "after setPath, mCurrentPath ==  " + mCurrentPath);
297
298        return ResponseCodes.OBEX_HTTP_OK;
299    }
300
301    @Override
302    public void onClose() {
303        if (mCallback != null) {
304            Message msg = Message.obtain(mCallback);
305            msg.what = BluetoothPbapService.MSG_SERVERSESSION_CLOSE;
306            msg.sendToTarget();
307            if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out.");
308        }
309    }
310
311    @Override
312    public int onGet(Operation op) {
313        sIsAborted = false;
314        HeaderSet request = null;
315        HeaderSet reply = new HeaderSet();
316        String type = "";
317        String name = "";
318        byte[] appParam = null;
319        AppParamValue appParamValue = new AppParamValue();
320        try {
321            request = op.getReceivedHeader();
322            type = (String)request.getHeader(HeaderSet.TYPE);
323            name = (String)request.getHeader(HeaderSet.NAME);
324            appParam = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
325        } catch (IOException e) {
326            Log.e(TAG, "request headers error");
327            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
328        }
329
330        if (V) logHeader(request);
331        if (D) Log.d(TAG, "OnGet type is " + type + "; name is " + name);
332
333        if (type == null) {
334            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
335        }
336        // Accroding to specification,the name header could be omitted such as
337        // sony erriccsonHBH-DS980
338
339        // For "x-bt/phonebook" and "x-bt/vcard-listing":
340        // if name == null, guess what carkit actually want from current path
341        // For "x-bt/vcard":
342        // We decide which kind of content client would like per current path
343
344        boolean validName = true;
345        if (TextUtils.isEmpty(name)) {
346            validName = false;
347        }
348
349        if (!validName || (validName && type.equals(TYPE_VCARD))) {
350            if (D) Log.d(TAG, "Guess what carkit actually want from current path (" +
351                    mCurrentPath + ")");
352
353            if (mCurrentPath.equals(PB_PATH)) {
354                appParamValue.needTag = ContentType.PHONEBOOK;
355            } else if (mCurrentPath.equals(ICH_PATH)) {
356                appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY;
357            } else if (mCurrentPath.equals(OCH_PATH)) {
358                appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY;
359            } else if (mCurrentPath.equals(MCH_PATH)) {
360                appParamValue.needTag = ContentType.MISSED_CALL_HISTORY;
361                mNeedNewMissedCallsNum = true;
362            } else if (mCurrentPath.equals(CCH_PATH)) {
363                appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY;
364            } else {
365                Log.w(TAG, "mCurrentpath is not valid path!!!");
366                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
367            }
368            if (D) Log.v(TAG, "onGet(): appParamValue.needTag=" + appParamValue.needTag);
369        } else {
370            // Not support SIM card currently
371            if (name.contains(SIM1.subSequence(0, SIM1.length()))) {
372                Log.w(TAG, "Not support access SIM card info!");
373                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
374            }
375
376            // we have weak name checking here to provide better
377            // compatibility with other devices,although unique name such as
378            // "pb.vcf" is required by SIG spec.
379            if (name.contains(PB.subSequence(0, PB.length()))) {
380                appParamValue.needTag = ContentType.PHONEBOOK;
381                if (D) Log.v(TAG, "download phonebook request");
382            } else if (name.contains(ICH.subSequence(0, ICH.length()))) {
383                appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY;
384                if (D) Log.v(TAG, "download incoming calls request");
385            } else if (name.contains(OCH.subSequence(0, OCH.length()))) {
386                appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY;
387                if (D) Log.v(TAG, "download outgoing calls request");
388            } else if (name.contains(MCH.subSequence(0, MCH.length()))) {
389                appParamValue.needTag = ContentType.MISSED_CALL_HISTORY;
390                mNeedNewMissedCallsNum = true;
391                if (D) Log.v(TAG, "download missed calls request");
392            } else if (name.contains(CCH.subSequence(0, CCH.length()))) {
393                appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY;
394                if (D) Log.v(TAG, "download combined calls request");
395            } else {
396                Log.w(TAG, "Input name doesn't contain valid info!!!");
397                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
398            }
399        }
400
401        if ((appParam != null) && !parseApplicationParameter(appParam, appParamValue)) {
402            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
403        }
404
405        // listing request
406        if (type.equals(TYPE_LISTING)) {
407            return pullVcardListing(appParam, appParamValue, reply, op);
408        }
409        // pull vcard entry request
410        else if (type.equals(TYPE_VCARD)) {
411            return pullVcardEntry(appParam, appParamValue, op, name, mCurrentPath);
412        }
413        // down load phone book request
414        else if (type.equals(TYPE_PB)) {
415            return pullPhonebook(appParam, appParamValue, reply, op, name);
416        } else {
417            Log.w(TAG, "unknown type request!!!");
418            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
419        }
420    }
421
422    /** check whether path is legal */
423    private final boolean isLegalPath(final String str) {
424        if (str.length() == 0) {
425            return true;
426        }
427        for (int i = 0; i < LEGAL_PATH.length; i++) {
428            if (str.equals(LEGAL_PATH[i])) {
429                return true;
430            }
431        }
432        return false;
433    }
434
435    private class AppParamValue {
436        public int maxListCount;
437
438        public int listStartOffset;
439
440        public String searchValue;
441
442        // Indicate which vCard parameter the search operation shall be carried
443        // out on. Can be "Name | Number | Sound", default value is "Name".
444        public String searchAttr;
445
446        // Indicate which sorting order shall be used for the
447        // <x-bt/vcard-listing> listing object.
448        // Can be "Alphabetical | Indexed | Phonetical", default value is
449        // "Indexed".
450        public String order;
451
452        public int needTag;
453
454        public boolean vcard21;
455
456        public AppParamValue() {
457            maxListCount = 0xFFFF;
458            listStartOffset = 0;
459            searchValue = "";
460            searchAttr = "";
461            order = "";
462            needTag = 0x00;
463            vcard21 = true;
464        }
465
466        public void dump() {
467            Log.i(TAG, "maxListCount=" + maxListCount + " listStartOffset=" + listStartOffset
468                    + " searchValue=" + searchValue + " searchAttr=" + searchAttr + " needTag="
469                    + needTag + " vcard21=" + vcard21 + " order=" + order);
470        }
471    }
472
473    /** To parse obex application parameter */
474    private final boolean parseApplicationParameter(final byte[] appParam,
475            AppParamValue appParamValue) {
476        int i = 0;
477        boolean parseOk = true;
478        while (i < appParam.length) {
479            switch (appParam[i]) {
480                case ApplicationParameter.TRIPLET_TAGID.FILTER_TAGID:
481                    i += 2; // length and tag field in triplet
482                    i += ApplicationParameter.TRIPLET_LENGTH.FILTER_LENGTH;
483                    break;
484                case ApplicationParameter.TRIPLET_TAGID.ORDER_TAGID:
485                    i += 2; // length and tag field in triplet
486                    appParamValue.order = Byte.toString(appParam[i]);
487                    i += ApplicationParameter.TRIPLET_LENGTH.ORDER_LENGTH;
488                    break;
489                case ApplicationParameter.TRIPLET_TAGID.SEARCH_VALUE_TAGID:
490                    i += 1; // length field in triplet
491                    // length of search value is variable
492                    int length = appParam[i];
493                    if (length == 0) {
494                        parseOk = false;
495                        break;
496                    }
497                    if (appParam[i+length] == 0x0) {
498                        appParamValue.searchValue = new String(appParam, i + 1, length-1);
499                    } else {
500                        appParamValue.searchValue = new String(appParam, i + 1, length);
501                    }
502                    i += length;
503                    i += 1;
504                    break;
505                case ApplicationParameter.TRIPLET_TAGID.SEARCH_ATTRIBUTE_TAGID:
506                    i += 2;
507                    appParamValue.searchAttr = Byte.toString(appParam[i]);
508                    i += ApplicationParameter.TRIPLET_LENGTH.SEARCH_ATTRIBUTE_LENGTH;
509                    break;
510                case ApplicationParameter.TRIPLET_TAGID.MAXLISTCOUNT_TAGID:
511                    i += 2;
512                    if (appParam[i] == 0 && appParam[i + 1] == 0) {
513                        mNeedPhonebookSize = true;
514                    } else {
515                        int highValue = appParam[i] & 0xff;
516                        int lowValue = appParam[i + 1] & 0xff;
517                        appParamValue.maxListCount = highValue * 256 + lowValue;
518                    }
519                    i += ApplicationParameter.TRIPLET_LENGTH.MAXLISTCOUNT_LENGTH;
520                    break;
521                case ApplicationParameter.TRIPLET_TAGID.LISTSTARTOFFSET_TAGID:
522                    i += 2;
523                    int highValue = appParam[i] & 0xff;
524                    int lowValue = appParam[i + 1] & 0xff;
525                    appParamValue.listStartOffset = highValue * 256 + lowValue;
526                    i += ApplicationParameter.TRIPLET_LENGTH.LISTSTARTOFFSET_LENGTH;
527                    break;
528                case ApplicationParameter.TRIPLET_TAGID.FORMAT_TAGID:
529                    i += 2;// length field in triplet
530                    if (appParam[i] != 0) {
531                        appParamValue.vcard21 = false;
532                    }
533                    i += ApplicationParameter.TRIPLET_LENGTH.FORMAT_LENGTH;
534                    break;
535                default:
536                    parseOk = false;
537                    Log.e(TAG, "Parse Application Parameter error");
538                    break;
539            }
540        }
541
542        if (D) appParamValue.dump();
543
544        return parseOk;
545    }
546
547    /** Form and Send an XML format String to client for Phone book listing */
548    private final int sendVcardListingXml(final int type, Operation op,
549            final int maxListCount, final int listStartOffset, final String searchValue,
550            String searchAttr) {
551        StringBuilder result = new StringBuilder();
552        int itemsFound = 0;
553        result.append("<?xml version=\"1.0\"?>");
554        result.append("<!DOCTYPE vcard-listing SYSTEM \"vcard-listing.dtd\">");
555        result.append("<vCard-listing version=\"1.0\">");
556
557        // Phonebook listing request
558        if (type == ContentType.PHONEBOOK) {
559            if (searchAttr.equals("0")) { // search by name
560                itemsFound = createList(maxListCount, listStartOffset, searchValue, result,
561                        "name");
562            } else if (searchAttr.equals("1")) { // search by number
563                itemsFound = createList(maxListCount, listStartOffset, searchValue, result,
564                        "number");
565            }// end of search by number
566            else {
567                return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
568            }
569        }
570        // Call history listing request
571        else {
572            ArrayList<String> nameList = mVcardManager.loadCallHistoryList(type);
573            int requestSize = nameList.size() >= maxListCount ? maxListCount : nameList.size();
574            int startPoint = listStartOffset;
575            int endPoint = startPoint + requestSize;
576            if (endPoint > nameList.size()) {
577                endPoint = nameList.size();
578            }
579            if (D) Log.d(TAG, "call log list, size=" + requestSize + " offset=" + listStartOffset);
580
581            for (int j = startPoint; j < endPoint; j++) {
582                // listing object begin with 1.vcf
583                result.append("<card handle=\"" + (j + 1) + ".vcf\" name=\"" + nameList.get(j)
584                        + "\"" + "/>");
585                itemsFound++;
586            }
587        }
588        result.append("</vCard-listing>");
589
590        if (V) Log.v(TAG, "itemsFound =" + itemsFound);
591
592        return pushBytes(op, result.toString());
593    }
594
595    private int createList(final int maxListCount, final int listStartOffset,
596            final String searchValue, StringBuilder result, String type) {
597        int itemsFound = 0;
598        ArrayList<String> nameList = mVcardManager.getPhonebookNameList(mOrderBy);
599        final int requestSize = nameList.size() >= maxListCount ? maxListCount : nameList.size();
600        final int listSize = nameList.size();
601        String compareValue = "", currentValue;
602
603        if (D) Log.d(TAG, "search by " + type + ", requestSize=" + requestSize + " offset="
604                    + listStartOffset + " searchValue=" + searchValue);
605
606        if (type.equals("number")) {
607            // query the number, to get the names
608            ArrayList<String> names = mVcardManager.getContactNamesByNumber(searchValue);
609            for (int i = 0; i < names.size(); i++) {
610                compareValue = names.get(i).trim();
611                if (D) Log.d(TAG, "compareValue=" + compareValue);
612                for (int pos = listStartOffset; pos < listSize &&
613                        itemsFound < requestSize; pos++) {
614                    currentValue = nameList.get(pos);
615                    if (D) Log.d(TAG, "currentValue=" + currentValue);
616                    if (currentValue.startsWith(compareValue)) {
617                        itemsFound++;
618                        result.append("<card handle=\"" + pos + ".vcf\" name=\""
619                                + currentValue + "\"" + "/>");
620                    }
621                }
622                if (itemsFound >= requestSize) {
623                    break;
624                }
625            }
626        } else {
627            if (searchValue != null) {
628                compareValue = searchValue.trim();
629            }
630            for (int pos = listStartOffset; pos < listSize &&
631                    itemsFound < requestSize; pos++) {
632                currentValue = nameList.get(pos);
633                if (D) Log.d(TAG, "currentValue=" + currentValue);
634                if (searchValue == null || currentValue.startsWith(compareValue)) {
635                    itemsFound++;
636                    result.append("<card handle=\"" + pos + ".vcf\" name=\""
637                            + currentValue + "\"" + "/>");
638                }
639            }
640        }
641        return itemsFound;
642    }
643
644    /**
645     * Function to send obex header back to client such as get phonebook size
646     * request
647     */
648    private final int pushHeader(final Operation op, final HeaderSet reply) {
649        OutputStream outputStream = null;
650
651        if (D) Log.d(TAG, "Push Header");
652        if (D) Log.d(TAG, reply.toString());
653
654        int pushResult = ResponseCodes.OBEX_HTTP_OK;
655        try {
656            op.sendHeaders(reply);
657            outputStream = op.openOutputStream();
658            outputStream.flush();
659        } catch (IOException e) {
660            Log.e(TAG, e.toString());
661            pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
662        } finally {
663            if (!closeStream(outputStream, op)) {
664                pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
665            }
666        }
667        return pushResult;
668    }
669
670    /** Function to send vcard data to client */
671    private final int pushBytes(Operation op, final String vcardString) {
672        if (vcardString == null) {
673            Log.w(TAG, "vcardString is null!");
674            return ResponseCodes.OBEX_HTTP_OK;
675        }
676
677        int vcardStringLen = vcardString.length();
678        if (D) Log.d(TAG, "Send Data: len=" + vcardStringLen);
679
680        OutputStream outputStream = null;
681        int pushResult = ResponseCodes.OBEX_HTTP_OK;
682        try {
683            outputStream = op.openOutputStream();
684        } catch (IOException e) {
685            Log.e(TAG, "open outputstrem failed" + e.toString());
686            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
687        }
688
689        int position = 0;
690        long timestamp = 0;
691        int outputBufferSize = op.getMaxPacketSize();
692        if (V) Log.v(TAG, "outputBufferSize = " + outputBufferSize);
693        while (position != vcardStringLen) {
694            if (sIsAborted) {
695                ((ServerOperation)op).isAborted = true;
696                sIsAborted = false;
697                break;
698            }
699            if (V) timestamp = System.currentTimeMillis();
700            int readLength = outputBufferSize;
701            if (vcardStringLen - position < outputBufferSize) {
702                readLength = vcardStringLen - position;
703            }
704            String subStr = vcardString.substring(position, position + readLength);
705            try {
706                outputStream.write(subStr.getBytes(), 0, readLength);
707            } catch (IOException e) {
708                Log.e(TAG, "write outputstrem failed" + e.toString());
709                pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
710                break;
711            }
712            if (V) {
713                Log.v(TAG, "Sending vcard String position = " + position + " readLength "
714                        + readLength + " bytes took " + (System.currentTimeMillis() - timestamp)
715                        + " ms");
716            }
717            position += readLength;
718        }
719
720        if (V) Log.v(TAG, "Send Data complete!");
721
722        if (!closeStream(outputStream, op)) {
723            pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
724        }
725
726        return pushResult;
727    }
728
729    private final int handleAppParaForResponse(AppParamValue appParamValue, int size,
730            HeaderSet reply, Operation op) {
731        byte[] misnum = new byte[1];
732        ApplicationParameter ap = new ApplicationParameter();
733
734        // In such case, PCE only want the number of index.
735        // So response not contain any Body header.
736        if (mNeedPhonebookSize) {
737            if (V) Log.v(TAG, "Need Phonebook size in response header.");
738            mNeedPhonebookSize = false;
739
740            byte[] pbsize = new byte[2];
741
742            pbsize[0] = (byte)((size / 256) & 0xff);// HIGH VALUE
743            pbsize[1] = (byte)((size % 256) & 0xff);// LOW VALUE
744            ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID,
745                    ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH, pbsize);
746
747            if (mNeedNewMissedCallsNum) {
748                int nmnum = size - mMissedCallSize;
749                mMissedCallSize = size;
750
751                nmnum = nmnum > 0 ? nmnum : 0;
752                misnum[0] = (byte)nmnum;
753                ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
754                        ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
755                if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= "
756                            + nmnum);
757            }
758            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
759
760            if (D) Log.d(TAG, "Send back Phonebook size only, without body info! Size= " + size);
761
762            return pushHeader(op, reply);
763        }
764
765        // Only apply to "mch" download/listing.
766        // NewMissedCalls is used only in the response, together with Body
767        // header.
768        if (mNeedNewMissedCallsNum) {
769            if (V) Log.v(TAG, "Need new missed call num in response header.");
770            mNeedNewMissedCallsNum = false;
771
772            int nmnum = size - mMissedCallSize;
773            mMissedCallSize = size;
774
775            nmnum = nmnum > 0 ? nmnum : 0;
776            misnum[0] = (byte)nmnum;
777            ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
778                    ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
779            reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
780            if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= "
781                        + nmnum);
782
783            // Only Specifies the headers, not write for now, will write to PCE
784            // together with Body
785            try {
786                op.sendHeaders(reply);
787            } catch (IOException e) {
788                Log.e(TAG, e.toString());
789                return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
790            }
791        }
792        return NEED_SEND_BODY;
793    }
794
795    private final int pullVcardListing(byte[] appParam, AppParamValue appParamValue,
796            HeaderSet reply, Operation op) {
797        String searchAttr = appParamValue.searchAttr.trim();
798
799        if (searchAttr == null || searchAttr.length() == 0) {
800            // If searchAttr is not set by PCE, set default value per spec.
801            appParamValue.searchAttr = "0";
802            if (D) Log.d(TAG, "searchAttr is not set by PCE, assume search by name by default");
803        } else if (!searchAttr.equals("0") && !searchAttr.equals("1")) {
804            Log.w(TAG, "search attr not supported");
805            if (searchAttr.equals("2")) {
806                // search by sound is not supported currently
807                Log.w(TAG, "do not support search by sound");
808                return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
809            }
810            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
811        } else {
812            Log.i(TAG, "searchAttr is valid: " + searchAttr);
813        }
814
815        int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
816        int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op);
817        if (needSendBody != NEED_SEND_BODY) {
818            return needSendBody;
819        }
820
821        if (size == 0) {
822            if (V) Log.v(TAG, "PhonebookSize is 0, return.");
823            return ResponseCodes.OBEX_HTTP_OK;
824        }
825
826        String orderPara = appParamValue.order.trim();
827        if (TextUtils.isEmpty(orderPara)) {
828            // If order parameter is not set by PCE, set default value per spec.
829            orderPara = "0";
830            if (D) Log.d(TAG, "Order parameter is not set by PCE. " +
831                       "Assume order by 'Indexed' by default");
832        } else if (!orderPara.equals("0") && !orderPara.equals("1")) {
833            if (V) Log.v(TAG, "Order parameter is not supported: " + appParamValue.order);
834            if (orderPara.equals("2")) {
835                // Order by sound is not supported currently
836                Log.w(TAG, "Do not support order by sound");
837                return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
838            }
839            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
840        } else {
841            Log.i(TAG, "Order parameter is valid: " + orderPara);
842        }
843
844        if (orderPara.equals("0")) {
845            mOrderBy = ORDER_BY_INDEXED;
846        } else if (orderPara.equals("1")) {
847            mOrderBy = ORDER_BY_ALPHABETICAL;
848        }
849
850        int sendResult = sendVcardListingXml(appParamValue.needTag, op, appParamValue.maxListCount,
851                appParamValue.listStartOffset, appParamValue.searchValue,
852                appParamValue.searchAttr);
853        return sendResult;
854    }
855
856    private final int pullVcardEntry(byte[] appParam, AppParamValue appParamValue,
857            Operation op, final String name, final String current_path) {
858        if (name == null || name.length() < VCARD_NAME_SUFFIX_LENGTH) {
859            if (D) Log.d(TAG, "Name is Null, or the length of name < 5 !");
860            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
861        }
862        String strIndex = name.substring(0, name.length() - VCARD_NAME_SUFFIX_LENGTH + 1);
863        int intIndex = 0;
864        if (strIndex.trim().length() != 0) {
865            try {
866                intIndex = Integer.parseInt(strIndex);
867            } catch (NumberFormatException e) {
868                Log.e(TAG, "catch number format exception " + e.toString());
869                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
870            }
871        }
872
873        int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
874        if (size == 0) {
875            if (V) Log.v(TAG, "PhonebookSize is 0, return.");
876            return ResponseCodes.OBEX_HTTP_OK;
877        }
878
879        boolean vcard21 = appParamValue.vcard21;
880        if (appParamValue.needTag == 0) {
881            Log.w(TAG, "wrong path!");
882            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
883        } else if (appParamValue.needTag == ContentType.PHONEBOOK) {
884            if (intIndex < 0 || intIndex >= size) {
885                Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
886                return ResponseCodes.OBEX_HTTP_OK;
887            } else if (intIndex == 0) {
888                // For PB_PATH, 0.vcf is the phone number of this phone.
889                String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21);
890                return pushBytes(op, ownerVcard);
891            } else {
892                return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null,
893                        mOrderBy );
894            }
895        } else {
896            if (intIndex <= 0 || intIndex > size) {
897                Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
898                return ResponseCodes.OBEX_HTTP_OK;
899            }
900            // For others (ich/och/cch/mch), 0.vcf is meaningless, and must
901            // begin from 1.vcf
902            if (intIndex >= 1) {
903                return mVcardManager.composeAndSendCallLogVcards(appParamValue.needTag, op,
904                        intIndex, intIndex, vcard21);
905            }
906        }
907        return ResponseCodes.OBEX_HTTP_OK;
908    }
909
910    private final int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply,
911            Operation op, final String name) {
912        // code start for passing PTS3.2 TC_PSE_PBD_BI_01_C
913        if (name != null) {
914            int dotIndex = name.indexOf(".");
915            String vcf = "vcf";
916            if (dotIndex >= 0 && dotIndex <= name.length()) {
917                if (name.regionMatches(dotIndex + 1, vcf, 0, vcf.length()) == false) {
918                    Log.w(TAG, "name is not .vcf");
919                    return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
920                }
921            }
922        } // code end for passing PTS3.2 TC_PSE_PBD_BI_01_C
923
924        int pbSize = mVcardManager.getPhonebookSize(appParamValue.needTag);
925        int needSendBody = handleAppParaForResponse(appParamValue, pbSize, reply, op);
926        if (needSendBody != NEED_SEND_BODY) {
927            return needSendBody;
928        }
929
930        if (pbSize == 0) {
931            if (V) Log.v(TAG, "PhonebookSize is 0, return.");
932            return ResponseCodes.OBEX_HTTP_OK;
933        }
934
935        int requestSize = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount
936                : pbSize;
937        int startPoint = appParamValue.listStartOffset;
938        if (startPoint < 0 || startPoint >= pbSize) {
939            Log.w(TAG, "listStartOffset is not correct! " + startPoint);
940            return ResponseCodes.OBEX_HTTP_OK;
941        }
942
943        // Limit the number of call log to CALLLOG_NUM_LIMIT
944        if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) {
945            if (requestSize > CALLLOG_NUM_LIMIT) {
946               requestSize = CALLLOG_NUM_LIMIT;
947            }
948        }
949
950        int endPoint = startPoint + requestSize - 1;
951        if (endPoint > pbSize - 1) {
952            endPoint = pbSize - 1;
953        }
954        if (D) Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" +
955                startPoint + " endPoint=" + endPoint);
956
957        String result = null;
958        boolean vcard21 = appParamValue.vcard21;
959        if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) {
960            if (startPoint == 0) {
961                String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21);
962                if (endPoint == 0) {
963                    return pushBytes(op, ownerVcard);
964                } else {
965                    return mVcardManager.composeAndSendPhonebookVcards(op, 1, endPoint, vcard21,
966                            ownerVcard);
967                }
968            } else {
969                return mVcardManager.composeAndSendPhonebookVcards(op, startPoint, endPoint,
970                        vcard21, null);
971            }
972        } else {
973            return mVcardManager.composeAndSendCallLogVcards(appParamValue.needTag, op,
974                    startPoint + 1, endPoint + 1, vcard21);
975        }
976    }
977
978    public static boolean closeStream(final OutputStream out, final Operation op) {
979        boolean returnvalue = true;
980        try {
981            if (out != null) {
982                out.close();
983            }
984        } catch (IOException e) {
985            Log.e(TAG, "outputStream close failed" + e.toString());
986            returnvalue = false;
987        }
988        try {
989            if (op != null) {
990                op.close();
991            }
992        } catch (IOException e) {
993            Log.e(TAG, "operation close failed" + e.toString());
994            returnvalue = false;
995        }
996        return returnvalue;
997    }
998
999    // Reserved for future use. In case PSE challenge PCE and PCE input wrong
1000    // session key.
1001    public final void onAuthenticationFailure(final byte[] userName) {
1002    }
1003
1004    public static final String createSelectionPara(final int type) {
1005        String selection = null;
1006        switch (type) {
1007            case ContentType.INCOMING_CALL_HISTORY:
1008                selection = Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE;
1009                break;
1010            case ContentType.OUTGOING_CALL_HISTORY:
1011                selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE;
1012                break;
1013            case ContentType.MISSED_CALL_HISTORY:
1014                selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE;
1015                break;
1016            default:
1017                break;
1018        }
1019        if (V) Log.v(TAG, "Call log selection: " + selection);
1020        return selection;
1021    }
1022
1023    public static final void logHeader(HeaderSet hs) {
1024        Log.v(TAG, "Dumping HeaderSet " + hs.toString());
1025        try {
1026
1027            Log.v(TAG, "COUNT : " + hs.getHeader(HeaderSet.COUNT));
1028            Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME));
1029            Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE));
1030            Log.v(TAG, "LENGTH : " + hs.getHeader(HeaderSet.LENGTH));
1031            Log.v(TAG, "TIME_ISO_8601 : " + hs.getHeader(HeaderSet.TIME_ISO_8601));
1032            Log.v(TAG, "TIME_4_BYTE : " + hs.getHeader(HeaderSet.TIME_4_BYTE));
1033            Log.v(TAG, "DESCRIPTION : " + hs.getHeader(HeaderSet.DESCRIPTION));
1034            Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET));
1035            Log.v(TAG, "HTTP : " + hs.getHeader(HeaderSet.HTTP));
1036            Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO));
1037            Log.v(TAG, "OBJECT_CLASS : " + hs.getHeader(HeaderSet.OBJECT_CLASS));
1038            Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER));
1039        } catch (IOException e) {
1040            Log.e(TAG, "dump HeaderSet error " + e);
1041        }
1042    }
1043}
1044