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