BluetoothPbapVcardManager.java revision 145880aa83b9e88d31daad5d47f250840573824e
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                    long id = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
268                    if (TextUtils.isEmpty(name)) {
269                        name = mContext.getString(android.R.string.unknownName);
270                    }
271                    nameList.add(name + "," + id);
272                }
273            }
274        } finally {
275            if (contactCursor != null) {
276                contactCursor.close();
277            }
278        }
279        return nameList;
280    }
281
282    public final ArrayList<String> getContactNamesByNumber(final String phoneNumber) {
283        ArrayList<String> nameList = new ArrayList<String>();
284
285        Cursor contactCursor = null;
286        Uri uri = null;
287
288        if (phoneNumber != null && phoneNumber.length() == 0) {
289            uri = Contacts.CONTENT_URI;
290        } else {
291            uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
292                Uri.encode(phoneNumber));
293        }
294
295        try {
296            contactCursor = mResolver.query(uri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
297                        null, Contacts._ID);
298
299            if (contactCursor != null) {
300                for (contactCursor.moveToFirst(); !contactCursor.isAfterLast(); contactCursor
301                        .moveToNext()) {
302                    String name = contactCursor.getString(CONTACTS_NAME_COLUMN_INDEX);
303                    long id = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
304                    if (TextUtils.isEmpty(name)) {
305                        name = mContext.getString(android.R.string.unknownName);
306                    }
307                    if (V) Log.v(TAG, "got name " + name + " by number " + phoneNumber + " @" + id);
308                    nameList.add(name + "," + id);
309                }
310            }
311        } finally {
312            if (contactCursor != null) {
313                contactCursor.close();
314            }
315        }
316        return nameList;
317    }
318
319    public final int composeAndSendCallLogVcards(final int type, Operation op,
320            final int startPoint, final int endPoint, final boolean vcardType21) {
321        if (startPoint < 1 || startPoint > endPoint) {
322            Log.e(TAG, "internal error: startPoint or endPoint is not correct.");
323            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
324        }
325        String typeSelection = BluetoothPbapObexServer.createSelectionPara(type);
326
327        final Uri myUri = CallLog.Calls.CONTENT_URI;
328        final String[] CALLLOG_PROJECTION = new String[] {
329            CallLog.Calls._ID, // 0
330        };
331        final int ID_COLUMN_INDEX = 0;
332
333        Cursor callsCursor = null;
334        long startPointId = 0;
335        long endPointId = 0;
336        try {
337            // Need test to see if order by _ID is ok here, or by date?
338            callsCursor = mResolver.query(myUri, CALLLOG_PROJECTION, typeSelection, null,
339                    CALLLOG_SORT_ORDER);
340            if (callsCursor != null) {
341                callsCursor.moveToPosition(startPoint - 1);
342                startPointId = callsCursor.getLong(ID_COLUMN_INDEX);
343                if (V) Log.v(TAG, "Call Log query startPointId = " + startPointId);
344                if (startPoint == endPoint) {
345                    endPointId = startPointId;
346                } else {
347                    callsCursor.moveToPosition(endPoint - 1);
348                    endPointId = callsCursor.getLong(ID_COLUMN_INDEX);
349                }
350                if (V) Log.v(TAG, "Call log query endPointId = " + endPointId);
351            }
352        } finally {
353            if (callsCursor != null) {
354                callsCursor.close();
355            }
356        }
357
358        String recordSelection;
359        if (startPoint == endPoint) {
360            recordSelection = Calls._ID + "=" + startPointId;
361        } else {
362            // The query to call table is by "_id DESC" order, so change
363            // correspondingly.
364            recordSelection = Calls._ID + ">=" + endPointId + " AND " + Calls._ID + "<="
365                    + startPointId;
366        }
367
368        String selection;
369        if (typeSelection == null) {
370            selection = recordSelection;
371        } else {
372            selection = "(" + typeSelection + ") AND (" + recordSelection + ")";
373        }
374
375        if (V) Log.v(TAG, "Call log query selection is: " + selection);
376
377        return composeAndSendVCards(op, selection, vcardType21, null, false);
378    }
379
380    public final int composeAndSendPhonebookVcards(Operation op, final int startPoint,
381            final int endPoint, final boolean vcardType21, String ownerVCard) {
382        if (startPoint < 1 || startPoint > endPoint) {
383            Log.e(TAG, "internal error: startPoint or endPoint is not correct.");
384            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
385        }
386        final Uri myUri = Contacts.CONTENT_URI;
387
388        Cursor contactCursor = null;
389        long startPointId = 0;
390        long endPointId = 0;
391        try {
392            contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE, null,
393                    Contacts._ID);
394            if (contactCursor != null) {
395                contactCursor.moveToPosition(startPoint - 1);
396                startPointId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
397                if (V) Log.v(TAG, "Query startPointId = " + startPointId);
398                if (startPoint == endPoint) {
399                    endPointId = startPointId;
400                } else {
401                    contactCursor.moveToPosition(endPoint - 1);
402                    endPointId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
403                }
404                if (V) Log.v(TAG, "Query endPointId = " + endPointId);
405            }
406        } finally {
407            if (contactCursor != null) {
408                contactCursor.close();
409            }
410        }
411
412        final String selection;
413        if (startPoint == endPoint) {
414            selection = Contacts._ID + "=" + startPointId + " AND " + CLAUSE_ONLY_VISIBLE;
415        } else {
416            selection = Contacts._ID + ">=" + startPointId + " AND " + Contacts._ID + "<="
417                    + endPointId + " AND " + CLAUSE_ONLY_VISIBLE;
418        }
419
420        if (V) Log.v(TAG, "Query selection is: " + selection);
421
422        return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true);
423    }
424
425    public final int composeAndSendPhonebookOneVcard(Operation op, final int offset,
426            final boolean vcardType21, String ownerVCard, int orderByWhat) {
427        if (offset < 1) {
428            Log.e(TAG, "Internal error: offset is not correct.");
429            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
430        }
431        final Uri myUri = Contacts.CONTENT_URI;
432        Cursor contactCursor = null;
433        String selection = null;
434        long contactId = 0;
435        if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) {
436            try {
437                contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
438                        null, Contacts._ID);
439                if (contactCursor != null) {
440                    contactCursor.moveToPosition(offset - 1);
441                    contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
442                    if (V) Log.v(TAG, "Query startPointId = " + contactId);
443                }
444            } finally {
445                if (contactCursor != null) {
446                    contactCursor.close();
447                }
448            }
449        } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
450            try {
451                contactCursor = mResolver.query(myUri, CONTACTS_PROJECTION, CLAUSE_ONLY_VISIBLE,
452                        null, Contacts.DISPLAY_NAME);
453                if (contactCursor != null) {
454                    contactCursor.moveToPosition(offset - 1);
455                    contactId = contactCursor.getLong(CONTACTS_ID_COLUMN_INDEX);
456                    if (V) Log.v(TAG, "Query startPointId = " + contactId);
457                }
458            } finally {
459                if (contactCursor != null) {
460                    contactCursor.close();
461                }
462            }
463        } else {
464            Log.e(TAG, "Parameter orderByWhat is not supported!");
465            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
466        }
467        selection = Contacts._ID + "=" + contactId;
468
469        if (V) Log.v(TAG, "Query selection is: " + selection);
470
471        return composeAndSendVCards(op, selection, vcardType21, ownerVCard, true);
472    }
473
474    public final int composeAndSendVCards(Operation op, final String selection,
475            final boolean vcardType21, String ownerVCard, boolean isContacts) {
476        long timestamp = 0;
477        if (V) timestamp = System.currentTimeMillis();
478
479        if (isContacts) {
480            VCardComposer composer = null;
481            HandlerForStringBuffer buffer = null;
482            try {
483                // Currently only support Generic Vcard 2.1 and 3.0
484                int vcardType;
485                if (vcardType21) {
486                    vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC;
487                } else {
488                    vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC;
489                }
490
491                if (!BluetoothPbapConfig.includePhotosInVcard()) {
492                    vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT;
493                }
494
495                //Enhancement: customize Vcard based on preferences/settings and input from caller
496                composer = BluetoothPbapUtils.createFilteredVCardComposer(mContext, vcardType,null);
497                //End enhancement
498
499                // BT does want PAUSE/WAIT conversion while it doesn't want the other formatting
500                // done by vCard library by default.
501                composer.setPhoneNumberTranslationCallback(
502                        new VCardPhoneNumberTranslationCallback() {
503                            public String onValueReceived(
504                                    String rawValue, int type, String label, boolean isPrimary) {
505                                // 'p' and 'w' are the standard characters for pause and wait
506                                // (see RFC 3601)
507                                // so use those when exporting phone numbers via vCard.
508                                String numberWithControlSequence = rawValue
509                                        .replace(PhoneNumberUtils.PAUSE, 'p')
510                                        .replace(PhoneNumberUtils.WAIT, 'w');
511                                return numberWithControlSequence;
512                            }
513                        });
514                buffer = new HandlerForStringBuffer(op, ownerVCard);
515                if (!composer.init(Contacts.CONTENT_URI, selection, null, Contacts._ID) ||
516                        !buffer.onInit(mContext)) {
517                    return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
518                }
519
520                while (!composer.isAfterLast()) {
521                    if (BluetoothPbapObexServer.sIsAborted) {
522                        ((ServerOperation)op).isAborted = true;
523                        BluetoothPbapObexServer.sIsAborted = false;
524                        break;
525                    }
526                    String vcard = composer.createOneEntry();
527                    if (vcard == null) {
528                        Log.e(TAG, "Failed to read a contact. Error reason: "
529                                + composer.getErrorReason());
530                        return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
531                    }
532                    if (V) {
533                        Log.v(TAG, "Vcard Entry:");
534                        Log.v(TAG,vcard);
535                    }
536
537                    if (!buffer.onEntryCreated(vcard)) {
538                        // onEntryCreate() already emits error.
539                        return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
540                    }
541                }
542            } finally {
543                if (composer != null) {
544                    composer.terminate();
545                }
546                if (buffer != null) {
547                    buffer.onTerminate();
548                }
549            }
550        } else { // CallLog
551            BluetoothPbapCallLogComposer composer = null;
552            HandlerForStringBuffer buffer = null;
553            try {
554
555                composer = new BluetoothPbapCallLogComposer(mContext);
556                buffer = new HandlerForStringBuffer(op, ownerVCard);
557                if (!composer.init(CallLog.Calls.CONTENT_URI, selection, null,
558                                   CALLLOG_SORT_ORDER) ||
559                                   !buffer.onInit(mContext)) {
560                    return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
561                }
562
563                while (!composer.isAfterLast()) {
564                    if (BluetoothPbapObexServer.sIsAborted) {
565                        ((ServerOperation)op).isAborted = true;
566                        BluetoothPbapObexServer.sIsAborted = false;
567                        break;
568                    }
569                    String vcard = composer.createOneEntry(vcardType21);
570                    if (vcard == null) {
571                        Log.e(TAG, "Failed to read a contact. Error reason: "
572                                + composer.getErrorReason());
573                        return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
574                    }
575                    if (V) {
576                        Log.v(TAG, "Vcard Entry:");
577                        Log.v(TAG,vcard);
578                    }
579
580                    buffer.onEntryCreated(vcard);
581                }
582            } finally {
583                if (composer != null) {
584                    composer.terminate();
585                }
586                if (buffer != null) {
587                    buffer.onTerminate();
588                }
589            }
590        }
591
592        if (V) Log.v(TAG, "Total vcard composing and sending out takes "
593                    + (System.currentTimeMillis() - timestamp) + " ms");
594
595        return ResponseCodes.OBEX_HTTP_OK;
596    }
597
598    /**
599     * Handler to emit vCards to PCE.
600     */
601    public class HandlerForStringBuffer {
602        private Operation operation;
603
604        private OutputStream outputStream;
605
606        private String phoneOwnVCard = null;
607
608        public HandlerForStringBuffer(Operation op, String ownerVCard) {
609            operation = op;
610            if (ownerVCard != null) {
611                phoneOwnVCard = ownerVCard;
612                if (V) Log.v(TAG, "phone own number vcard:");
613                if (V) Log.v(TAG, phoneOwnVCard);
614            }
615        }
616
617        private boolean write(String vCard) {
618            try {
619                if (vCard != null) {
620                    outputStream.write(vCard.getBytes());
621                    return true;
622                }
623            } catch (IOException e) {
624                Log.e(TAG, "write outputstrem failed" + e.toString());
625            }
626            return false;
627        }
628
629        public boolean onInit(Context context) {
630            try {
631                outputStream = operation.openOutputStream();
632                if (phoneOwnVCard != null) {
633                    return write(phoneOwnVCard);
634                }
635                return true;
636            } catch (IOException e) {
637                Log.e(TAG, "open outputstrem failed" + e.toString());
638            }
639            return false;
640        }
641
642        public boolean onEntryCreated(String vcard) {
643            return write(vcard);
644        }
645
646        public void onTerminate() {
647            if (!BluetoothPbapObexServer.closeStream(outputStream, operation)) {
648                if (V) Log.v(TAG, "CloseStream failed!");
649            } else {
650                if (V) Log.v(TAG, "CloseStream ok!");
651            }
652        }
653    }
654}
655