BluetoothPbapVcardManager.java revision c7a57f31515107366589bd6875c3cc4af1fc806e
1/*
2 * Copyright (c) 2008-2009, Motorola, Inc.
3 * Copyright (C) 2009-2012, Broadcom Corporation
4 *
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * - Redistributions of source code must retain the above copyright notice,
11 * this list of conditions and the following disclaimer.
12 *
13 * - Redistributions in binary form must reproduce the above copyright notice,
14 * this list of conditions and the following disclaimer in the documentation
15 * and/or other materials provided with the distribution.
16 *
17 * - Neither the name of the Motorola, Inc. nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 * POSSIBILITY OF SUCH DAMAGE.
32 */
33
34package com.android.bluetooth.pbap;
35
36import android.content.ContentResolver;
37import android.content.Context;
38import android.database.Cursor;
39import android.net.Uri;
40import android.provider.CallLog;
41import android.provider.CallLog.Calls;
42import android.provider.ContactsContract.CommonDataKinds;
43import android.provider.ContactsContract.Contacts;
44import android.provider.ContactsContract.Data;
45import android.provider.ContactsContract.CommonDataKinds.Phone;
46import android.provider.ContactsContract.PhoneLookup;
47import android.telephony.PhoneNumberUtils;
48import android.text.TextUtils;
49import android.util.Log;
50
51import com.android.bluetooth.R;
52import com.android.vcard.VCardComposer;
53import com.android.vcard.VCardConfig;
54import com.android.internal.telephony.CallerInfo;
55import com.android.vcard.VCardPhoneNumberTranslationCallback;
56
57import java.io.IOException;
58import java.io.OutputStream;
59import java.util.ArrayList;
60
61import javax.obex.ServerOperation;
62import javax.obex.Operation;
63import javax.obex.ResponseCodes;
64
65import com.android.bluetooth.Utils;
66
67public class BluetoothPbapVcardManager {
68    private static final String TAG = "BluetoothPbapVcardManager";
69
70    private static final boolean V = BluetoothPbapService.VERBOSE;
71
72    private ContentResolver mResolver;
73
74    private Context mContext;
75
76    static final String[] PHONES_PROJECTION = new String[] {
77            Data._ID, // 0
78            CommonDataKinds.Phone.TYPE, // 1
79            CommonDataKinds.Phone.LABEL, // 2
80            CommonDataKinds.Phone.NUMBER, // 3
81            Contacts.DISPLAY_NAME, // 4
82    };
83
84    private static final int PHONE_NUMBER_COLUMN_INDEX = 3;
85
86    static final String SORT_ORDER_PHONE_NUMBER = CommonDataKinds.Phone.NUMBER + " ASC";
87
88    static final String[] CONTACTS_PROJECTION = new String[] {
89            Contacts._ID, // 0
90            Contacts.DISPLAY_NAME, // 1
91    };
92
93    static final int CONTACTS_ID_COLUMN_INDEX = 0;
94
95    static final int CONTACTS_NAME_COLUMN_INDEX = 1;
96
97    // call histories use dynamic handles, and handles should order by date; the
98    // most recently one should be the first handle. In table "calls", _id and
99    // date are consistent in ordering, to implement simply, we sort by _id
100    // here.
101    static final String CALLLOG_SORT_ORDER = Calls._ID + " DESC";
102
103    private static final String CLAUSE_ONLY_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
104
105    public BluetoothPbapVcardManager(final Context context) {
106        mContext = context;
107        mResolver = mContext.getContentResolver();
108    }
109
110    /**
111     * Create an owner vcard from the configured profile
112     * @param vcardType21
113     * @return
114     */
115    private final String getOwnerPhoneNumberVcardFromProfile(final boolean vcardType21, final byte[] filter) {
116        // Currently only support Generic Vcard 2.1 and 3.0
117        int vcardType;
118        if (vcardType21) {
119            vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC;
120        } else {
121            vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC;
122        }
123        return BluetoothPbapUtils.createProfileVCard(mContext, vcardType,filter);
124    }
125
126    public final String getOwnerPhoneNumberVcard(final boolean vcardType21, final byte[] filter) {
127        //Owner vCard enhancement: Use "ME" profile if configured
128        if (BluetoothPbapConfig.useProfileForOwnerVcard()) {
129            String vcard = getOwnerPhoneNumberVcardFromProfile(vcardType21, filter);
130            if (vcard != null && vcard.length() != 0) {
131                return vcard;
132            }
133        }
134        //End enhancement
135
136        BluetoothPbapCallLogComposer composer = new BluetoothPbapCallLogComposer(mContext);
137        String name = BluetoothPbapService.getLocalPhoneName();
138        String number = BluetoothPbapService.getLocalPhoneNum();
139        String vcard = composer.composeVCardForPhoneOwnNumber(Phone.TYPE_MOBILE, name, number,
140                vcardType21);
141        return vcard;
142    }
143
144    public final int getPhonebookSize(final int type) {
145        int size;
146        switch (type) {
147            case BluetoothPbapObexServer.ContentType.PHONEBOOK:
148                size = getContactsSize();
149                break;
150            default:
151                size = getCallHistorySize(type);
152                break;
153        }
154        if (V) Log.v(TAG, "getPhonebookSize size = " + size + " type = " + type);
155        return size;
156    }
157
158    public final int getContactsSize() {
159        final Uri myUri = Contacts.CONTENT_URI;
160        int size = 0;
161        Cursor contactCursor = null;
162        try {
163            contactCursor = mResolver.query(myUri, null, CLAUSE_ONLY_VISIBLE, null, null);
164            if (contactCursor != null) {
165                size = contactCursor.getCount() + 1; // always has the 0.vcf
166            }
167        } finally {
168            if (contactCursor != null) {
169                contactCursor.close();
170            }
171        }
172        return size;
173    }
174
175    public final int getCallHistorySize(final int type) {
176        final Uri myUri = CallLog.Calls.CONTENT_URI;
177        String selection = BluetoothPbapObexServer.createSelectionPara(type);
178        int size = 0;
179        Cursor callCursor = null;
180        try {
181            callCursor = mResolver.query(myUri, null, selection, null,
182                    CallLog.Calls.DEFAULT_SORT_ORDER);
183            if (callCursor != null) {
184                size = callCursor.getCount();
185            }
186        } finally {
187            if (callCursor != null) {
188                callCursor.close();
189            }
190        }
191        return size;
192    }
193
194    public final ArrayList<String> loadCallHistoryList(final int type) {
195        final Uri myUri = CallLog.Calls.CONTENT_URI;
196        String selection = BluetoothPbapObexServer.createSelectionPara(type);
197        String[] projection = new String[] {
198                Calls.NUMBER, Calls.CACHED_NAME
199        };
200        final int CALLS_NUMBER_COLUMN_INDEX = 0;
201        final int CALLS_NAME_COLUMN_INDEX = 1;
202
203        Cursor callCursor = null;
204        ArrayList<String> list = new ArrayList<String>();
205        try {
206            callCursor = mResolver.query(myUri, projection, selection, null,
207                    CALLLOG_SORT_ORDER);
208            if (callCursor != null) {
209                for (callCursor.moveToFirst(); !callCursor.isAfterLast();
210                        callCursor.moveToNext()) {
211                    String name = callCursor.getString(CALLS_NAME_COLUMN_INDEX);
212                    if (TextUtils.isEmpty(name)) {
213                        // name not found, use number instead
214                        name = callCursor.getString(CALLS_NUMBER_COLUMN_INDEX);
215                        if (CallerInfo.UNKNOWN_NUMBER.equals(name) ||
216                                CallerInfo.PRIVATE_NUMBER.equals(name) ||
217                                CallerInfo.PAYPHONE_NUMBER.equals(name)) {
218                            name = mContext.getString(R.string.unknownNumber);
219                        }
220                    }
221                    list.add(name);
222                }
223            }
224        } finally {
225            if (callCursor != null) {
226                callCursor.close();
227            }
228        }
229        return list;
230    }
231
232    public final ArrayList<String> getPhonebookNameList(final int orderByWhat) {
233        ArrayList<String> nameList = new ArrayList<String>();
234        //Owner vCard enhancement. Use "ME" profile if configured
235        String ownerName = null;
236        if (BluetoothPbapConfig.useProfileForOwnerVcard()) {
237            ownerName = BluetoothPbapUtils.getProfileName(mContext);
238        }
239        if (ownerName == null || ownerName.length()==0) {
240            ownerName = BluetoothPbapService.getLocalPhoneName();
241        }
242        nameList.add(ownerName);
243        //End enhancement
244
245        final Uri myUri = Contacts.CONTENT_URI;
246        Cursor contactCursor = null;
247        try {
248            if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) {
249                if (V) Log.v(TAG, "getPhonebookNameList, order by index");
250                contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
251                        null, Contacts._ID);
252            } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
253                if (V) Log.v(TAG, "getPhonebookNameList, order by alpha");
254                contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
255                        null, Contacts.DISPLAY_NAME);
256            }
257            if (contactCursor != null) {
258                for (contactCursor.moveToFirst(); !contactCursor.isAfterLast(); contactCursor
259                        .moveToNext()) {
260                    String name = contactCursor.getString(CONTACTS_NAME_COLUMN_INDEX);
261                    if (TextUtils.isEmpty(name)) {
262                        name = mContext.getString(android.R.string.unknownName);
263                    }
264                    nameList.add(name);
265                }
266            }
267        } finally {
268            if (contactCursor != null) {
269                contactCursor.close();
270            }
271        }
272        return nameList;
273    }
274
275    public final ArrayList<String> getContactNamesByNumber(final String phoneNumber) {
276        ArrayList<String> nameList = new ArrayList<String>();
277
278        Cursor contactCursor = null;
279        Uri uri = null;
280
281        if (phoneNumber != null && phoneNumber.length() == 0) {
282            uri = Contacts.CONTENT_URI;
283        } else {
284            uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
285                Uri.encode(phoneNumber));
286        }
287
288        try {
289            contactCursor = mResolver.query(uri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
290                        null, Contacts._ID);
291
292            if (contactCursor != null) {
293                for (contactCursor.moveToFirst(); !contactCursor.isAfterLast(); contactCursor
294                        .moveToNext()) {
295                    String name = contactCursor.getString(CONTACTS_NAME_COLUMN_INDEX);
296                    long id = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
297                    if (TextUtils.isEmpty(name)) {
298                        name = mContext.getString(android.R.string.unknownName);
299                    }
300                    if (V) Log.v(TAG, "got name " + name + " by number " + phoneNumber + " @" + id);
301                    nameList.add(name);
302                }
303            }
304        } finally {
305            if (contactCursor != null) {
306                contactCursor.close();
307            }
308        }
309        return nameList;
310    }
311
312    public final int composeAndSendCallLogVcards(final int type, Operation op,
313            final int startPoint, final int endPoint, final boolean vcardType21) {
314        if (startPoint < 1 || startPoint > endPoint) {
315            Log.e(TAG, "internal error: startPoint or endPoint is not correct.");
316            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
317        }
318        String typeSelection = BluetoothPbapObexServer.createSelectionPara(type);
319
320        final Uri myUri = CallLog.Calls.CONTENT_URI;
321        final String[] CALLLOG_PROJECTION = new String[] {
322            CallLog.Calls._ID, // 0
323        };
324        final int ID_COLUMN_INDEX = 0;
325
326        Cursor callsCursor = null;
327        long startPointId = 0;
328        long endPointId = 0;
329        try {
330            // Need test to see if order by _ID is ok here, or by date?
331            callsCursor = mResolver.query(myUri, CALLLOG_PROJECTION, typeSelection, null,
332                    CALLLOG_SORT_ORDER);
333            if (callsCursor != null) {
334                callsCursor.moveToPosition(startPoint - 1);
335                startPointId = callsCursor.getLong(ID_COLUMN_INDEX);
336                if (V) Log.v(TAG, "Call Log query startPointId = " + startPointId);
337                if (startPoint == endPoint) {
338                    endPointId = startPointId;
339                } else {
340                    callsCursor.moveToPosition(endPoint - 1);
341                    endPointId = callsCursor.getLong(ID_COLUMN_INDEX);
342                }
343                if (V) Log.v(TAG, "Call log query endPointId = " + endPointId);
344            }
345        } finally {
346            if (callsCursor != null) {
347                callsCursor.close();
348            }
349        }
350
351        String recordSelection;
352        if (startPoint == endPoint) {
353            recordSelection = Calls._ID + "=" + startPointId;
354        } else {
355            // The query to call table is by "_id DESC" order, so change
356            // correspondingly.
357            recordSelection = Calls._ID + ">=" + endPointId + " AND " + Calls._ID + "<="
358                    + startPointId;
359        }
360
361        String selection;
362        if (typeSelection == null) {
363            selection = recordSelection;
364        } else {
365            selection = "(" + typeSelection + ") AND (" + recordSelection + ")";
366        }
367
368        if (V) Log.v(TAG, "Call log query selection is: " + selection);
369
370        return composeAndSendVCards(op, selection, vcardType21, null, false);
371    }
372
373    public final int composeAndSendPhonebookVcards(Operation op, final int startPoint,
374            final int endPoint, final boolean vcardType21, String ownerVCard) {
375        if (startPoint < 1 || startPoint > endPoint) {
376            Log.e(TAG, "internal error: startPoint or endPoint is not correct.");
377            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
378        }
379        final Uri myUri = Contacts.CONTENT_URI;
380
381        Cursor contactCursor = null;
382        long startPointId = 0;
383        long endPointId = 0;
384        try {
385            contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE, null,
386                    Contacts._ID);
387            if (contactCursor != null) {
388                contactCursor.moveToPosition(startPoint - 1);
389                startPointId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
390                if (V) Log.v(TAG, "Query startPointId = " + startPointId);
391                if (startPoint == endPoint) {
392                    endPointId = startPointId;
393                } else {
394                    contactCursor.moveToPosition(endPoint - 1);
395                    endPointId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
396                }
397                if (V) Log.v(TAG, "Query endPointId = " + endPointId);
398            }
399        } finally {
400            if (contactCursor != null) {
401                contactCursor.close();
402            }
403        }
404
405        final String selection;
406        if (startPoint == endPoint) {
407            selection = Contacts._ID + "=" + startPointId + " AND " + CLAUSE_ONLY_VISIBLE;
408        } else {
409            selection = Contacts._ID + ">=" + startPointId + " AND " + Contacts._ID + "<="
410                    + endPointId + " AND " + CLAUSE_ONLY_VISIBLE;
411        }
412
413        if (V) Log.v(TAG, "Query selection is: " + selection);
414
415        return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true);
416    }
417
418    public final int composeAndSendPhonebookOneVcard(Operation op, final int offset,
419            final boolean vcardType21, String ownerVCard, int orderByWhat) {
420        if (offset < 1) {
421            Log.e(TAG, "Internal error: offset is not correct.");
422            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
423        }
424        final Uri myUri = Contacts.CONTENT_URI;
425        Cursor contactCursor = null;
426        String selection = null;
427        long contactId = 0;
428        if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) {
429            try {
430                contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
431                        null, Contacts._ID);
432                if (contactCursor != null) {
433                    contactCursor.moveToPosition(offset - 1);
434                    contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
435                    if (V) Log.v(TAG, "Query startPointId = " + contactId);
436                }
437            } finally {
438                if (contactCursor != null) {
439                    contactCursor.close();
440                }
441            }
442        } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
443            try {
444                contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
445                        null, Contacts.DISPLAY_NAME);
446                if (contactCursor != null) {
447                    contactCursor.moveToPosition(offset - 1);
448                    contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
449                    if (V) Log.v(TAG, "Query startPointId = " + contactId);
450                }
451            } finally {
452                if (contactCursor != null) {
453                    contactCursor.close();
454                }
455            }
456        } else {
457            Log.e(TAG, "Parameter orderByWhat is not supported!");
458            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
459        }
460        selection = Contacts._ID + "=" + contactId;
461
462        if (V) Log.v(TAG, "Query selection is: " + selection);
463
464        return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true);
465    }
466
467    public final int composeAndSendVCards(Operation op, final String selection,
468            final boolean vcardType21, String ownerVCard, boolean isContacts) {
469        long timestamp = 0;
470        if (V) timestamp = System.currentTimeMillis();
471
472        if (isContacts) {
473            VCardComposer composer = null;
474            HandlerForStringBuffer buffer = null;
475            try {
476                // Currently only support Generic Vcard 2.1 and 3.0
477                int vcardType;
478                if (vcardType21) {
479                    vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC;
480                } else {
481                    vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC;
482                }
483                vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT;
484
485                //Enhancement: customize Vcard based on preferences/settings and input from caller
486                composer = BluetoothPbapUtils.createFilteredVCardComposer(mContext, vcardType,null);
487                //End enhancement
488
489                // BT does want PAUSE/WAIT conversion while it doesn't want the other formatting
490                // done by vCard library by default.
491                composer.setPhoneNumberTranslationCallback(
492                        new VCardPhoneNumberTranslationCallback() {
493                            public String onValueReceived(
494                                    String rawValue, int type, String label, boolean isPrimary) {
495                                // 'p' and 'w' are the standard characters for pause and wait
496                                // (see RFC 3601)
497                                // so use those when exporting phone numbers via vCard.
498                                String numberWithControlSequence = rawValue
499                                        .replace(PhoneNumberUtils.PAUSE, 'p')
500                                        .replace(PhoneNumberUtils.WAIT, 'w');
501                                return numberWithControlSequence;
502                            }
503                        });
504                buffer = new HandlerForStringBuffer(op, ownerVCard);
505                if (!composer.init(Contacts.CONTENT_URI, selection, null, Contacts._ID) ||
506                        !buffer.onInit(mContext)) {
507                    return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
508                }
509
510                while (!composer.isAfterLast()) {
511                    if (BluetoothPbapObexServer.sIsAborted) {
512                        ((ServerOperation)op).isAborted = true;
513                        BluetoothPbapObexServer.sIsAborted = false;
514                        break;
515                    }
516                    String vcard = composer.createOneEntry();
517                    if (vcard == null) {
518                        Log.e(TAG, "Failed to read a contact. Error reason: "
519                                + composer.getErrorReason());
520                        return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
521                    }
522                    if (V) {
523                        Log.v(TAG, "Vcard Entry:");
524                        Log.v(TAG,vcard);
525                    }
526
527                    if (!buffer.onEntryCreated(vcard)) {
528                        // onEntryCreate() already emits error.
529                        return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
530                    }
531                }
532            } finally {
533                if (composer != null) {
534                    composer.terminate();
535                }
536                if (buffer != null) {
537                    buffer.onTerminate();
538                }
539            }
540        } else { // CallLog
541            BluetoothPbapCallLogComposer composer = null;
542            HandlerForStringBuffer buffer = null;
543            try {
544
545                composer = new BluetoothPbapCallLogComposer(mContext);
546                buffer = new HandlerForStringBuffer(op, ownerVCard);
547                if (!composer.init(CallLog.Calls.CONTENT_URI, selection, null,
548                                   CALLLOG_SORT_ORDER) ||
549                                   !buffer.onInit(mContext)) {
550                    return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
551                }
552
553                while (!composer.isAfterLast()) {
554                    if (BluetoothPbapObexServer.sIsAborted) {
555                        ((ServerOperation)op).isAborted = true;
556                        BluetoothPbapObexServer.sIsAborted = false;
557                        break;
558                    }
559                    String vcard = composer.createOneEntry(vcardType21);
560                    if (vcard == null) {
561                        Log.e(TAG, "Failed to read a contact. Error reason: "
562                                + composer.getErrorReason());
563                        return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
564                    }
565                    if (V) {
566                        Log.v(TAG, "Vcard Entry:");
567                        Log.v(TAG,vcard);
568                    }
569
570                    buffer.onEntryCreated(vcard);
571                }
572            } finally {
573                if (composer != null) {
574                    composer.terminate();
575                }
576                if (buffer != null) {
577                    buffer.onTerminate();
578                }
579            }
580        }
581
582        if (V) Log.v(TAG, "Total vcard composing and sending out takes "
583                    + (System.currentTimeMillis() - timestamp) + " ms");
584
585        return ResponseCodes.OBEX_HTTP_OK;
586    }
587
588    /**
589     * Handler to emit vCards to PCE.
590     */
591    public class HandlerForStringBuffer {
592        private Operation operation;
593
594        private OutputStream outputStream;
595
596        private String phoneOwnVCard = null;
597
598        public HandlerForStringBuffer(Operation op, String ownerVCard) {
599            operation = op;
600            if (ownerVCard != null) {
601                phoneOwnVCard = ownerVCard;
602                if (V) Log.v(TAG, "phone own number vcard:");
603                if (V) Log.v(TAG, phoneOwnVCard);
604            }
605        }
606
607        private boolean write(String vCard) {
608            try {
609                if (vCard != null) {
610                    outputStream.write(vCard.getBytes());
611                    return true;
612                }
613            } catch (IOException e) {
614                Log.e(TAG, "write outputstrem failed" + e.toString());
615            }
616            return false;
617        }
618
619        public boolean onInit(Context context) {
620            try {
621                outputStream = operation.openOutputStream();
622                if (phoneOwnVCard != null) {
623                    return write(phoneOwnVCard);
624                }
625                return true;
626            } catch (IOException e) {
627                Log.e(TAG, "open outputstrem failed" + e.toString());
628            }
629            return false;
630        }
631
632        public boolean onEntryCreated(String vcard) {
633            return write(vcard);
634        }
635
636        public void onTerminate() {
637            if (!BluetoothPbapObexServer.closeStream(outputStream, operation)) {
638                if (V) Log.v(TAG, "CloseStream failed!");
639            } else {
640                if (V) Log.v(TAG, "CloseStream ok!");
641            }
642        }
643    }
644}
645