<uses-permission android:name="android.permission.NET_TUNNELING" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+ <!-- For PBAP Owner Vcard Info -->
+ <uses-permission android:name="android.permission.READ_PROFILE"/>
<application
android:name=".btservice.AdapterApp"
android:icon="@drawable/bt_share"
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009-2012 Broadcom Corporation
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <bool name="pbap_include_photos_in_vcard">true</bool>
+ <bool name="pbap_use_profile_for_owner_vcard">true</bool>
+</resources>
\ No newline at end of file
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.os.ParcelUuid;
+import android.util.Log;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.UUID;
*/
final public class Utils {
+ private static final String TAG = "BluetoothUtils";
static final int BD_ADDR_LEN = 6; // bytes
static final int BD_UUID_LEN = 16; // bytes
default : return "UNKNOWN";
}
}
+
+ public static void copyStream(InputStream is, OutputStream os, int bufferSize) throws IOException {
+ if (is != null && os!=null) {
+ byte[] buffer = new byte[bufferSize];
+ int bytesRead=0;
+ while ( (bytesRead = is.read(buffer))>=0) {
+ os.write(buffer,0,bytesRead);
+ }
+ }
+ }
+
+ public static void safeCloseStream(InputStream is) {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (Throwable t) {
+ Log.d(TAG,"Error closing stream",t);
+ }
+ }
+ }
+
+ public static void safeCloseStream(OutputStream os) {
+ if (os != null) {
+ try {
+ os.close();
+ } catch (Throwable t) {
+ Log.d(TAG,"Error closing stream",t);
+ }
+ }
+ }
}
--- /dev/null
+/************************************************************************************
+ *
+ * Copyright (C) 2009-2012 Broadcom Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ************************************************************************************/
+package com.android.bluetooth.pbap;
+
+import com.android.bluetooth.R;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+
+public class BluetoothPbapConfig {
+ private static boolean sUseProfileForOwnerVcard=true;
+ private static boolean sIncludePhotosInVcard = true;
+ public static void init(Context ctx) {
+ Resources r = ctx.getResources();
+ if (r != null) {
+ try {
+ sUseProfileForOwnerVcard = r.getBoolean(R.bool.pbap_use_profile_for_owner_vcard);
+ } catch(Exception e) {
+ Log.e("BluetoothPbapConfig","",e);
+ }
+ try {
+ sIncludePhotosInVcard = r.getBoolean(R.bool.pbap_include_photos_in_vcard);
+ } catch(Exception e) {
+ Log.e("BluetoothPbapConfig","",e);
+ }
+ }
+ }
+
+ /**
+ * If true, owner vcard will be generated from the "Me" profile
+ */
+ public static boolean useProfileForOwnerVcard() {
+ return sUseProfileForOwnerVcard;
+ }
+
+ /**
+ * If true, include photos in contact information returned to PCE
+ * @return
+ */
+ public static boolean includePhotosInVcard() {
+ return sIncludePhotosInVcard;
+ }
+}
return ResponseCodes.OBEX_HTTP_NOT_FOUND;
} else if (intIndex == 0) {
// For PB_PATH, 0.vcf is the phone number of this phone.
- String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21);
+ String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21,null);
return pushBytes(op, ownerVcard);
} else {
return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null,
boolean vcard21 = appParamValue.vcard21;
if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) {
if (startPoint == 0) {
- String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21);
+ String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21,null);
if (endPoint == 0) {
return pushBytes(op, ownerVcard);
} else {
if (!mHasStarted) {
mHasStarted = true;
if (VERBOSE) Log.v(TAG, "Starting PBAP service");
-
+ BluetoothPbapConfig.init(this);
int state = mAdapter.getState();
if (state == BluetoothAdapter.STATE_ON) {
mSessionStatusHandler.sendMessage(mSessionStatusHandler
--- /dev/null
+/************************************************************************************
+ *
+ * Copyright (C) 2009-2012 Broadcom Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ************************************************************************************/
+package com.android.bluetooth.pbap;
+
+import android.content.Context;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.pbap.BluetoothPbapService;
+import com.android.vcard.VCardComposer;
+import com.android.vcard.VCardConfig;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Profile;
+import android.provider.ContactsContract.RawContactsEntity;
+
+
+import com.android.vcard.VCardComposer;
+import com.android.vcard.VCardConfig;
+
+
+public class BluetoothPbapUtils {
+
+ private static final String TAG = "FilterUtils";
+ private static final boolean V = BluetoothPbapService.VERBOSE;
+
+ public static int FILTER_PHOTO = 3;
+ public static int FILTER_TEL = 7;
+ public static int FILTER_NICKNAME = 23;
+
+ public static boolean hasFilter(byte[] filter) {
+ return filter != null && filter.length > 0;
+ }
+
+ public static boolean isNameAndNumberOnly(byte[] filter) {
+ // For vcard 2.0: VERSION,N,TEL is mandatory
+ // For vcard 3.0, VERSION,N,FN,TEL is mandatory
+ // So we only need to make sure that no other fields except optionally
+ // NICKNAME is set
+
+ // Check that an explicit filter is not set. If not, this means
+ // return everything
+ if (!hasFilter(filter)) {
+ Log.v(TAG, "No filter set. isNameAndNumberOnly=false");
+ return false;
+ }
+
+ // Check bytes 0-4 are all 0
+ for (int i = 0; i <= 4; i++) {
+ if (filter[i] != 0) {
+ return false;
+ }
+ }
+ // On byte 5, only BIT_NICKNAME can be set, so make sure
+ // rest of bits are not set
+ if ((filter[5] & 0x7F) > 0) {
+ return false;
+ }
+
+ // Check byte 6 is not set
+ if (filter[6] != 0) {
+ return false;
+ }
+
+ // Check if bit#3-6 is set. Return false if so.
+ if ((filter[7] & 0x78) > 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static boolean isFilterBitSet(byte[] filter, int filterBit) {
+ if (hasFilter(filter)) {
+ int byteNumber = 7 - filterBit / 8;
+ int bitNumber = filterBit % 8;
+ if (byteNumber < filter.length) {
+ return (filter[byteNumber] & (1 << bitNumber)) > 0;
+ }
+ }
+ return false;
+ }
+
+ public static VCardComposer createFilteredVCardComposer(final Context ctx,
+ final int vcardType, final byte[] filter) {
+ int vType = vcardType;
+ /*
+ boolean isNameNumberOnly = isNameAndNumberOnly(filter);
+ if (isNameNumberOnly) {
+ if (V)
+ Log.v(TAG, "Creating Name/Number only VCardComposer...");
+ vType |= VCardConfig.FLAG_NAME_NUMBER_ONLY_EXPORT;
+ } else {
+ */
+ boolean includePhoto = BluetoothPbapConfig.includePhotosInVcard()
+ && (!hasFilter(filter) || isFilterBitSet(filter, FILTER_PHOTO));
+ if (!includePhoto) {
+ if (V) Log.v(TAG, "Excluding images from VCardComposer...");
+ vType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT;
+ }
+ //}
+ return new VCardComposer(ctx, vType, true);
+ }
+
+ public static boolean isProfileSet(Context context) {
+ Cursor c = context.getContentResolver().query(
+ Profile.CONTENT_VCARD_URI, new String[] { Profile._ID }, null,
+ null, null);
+ boolean isSet = (c != null && c.getCount() > 0);
+ if (c != null) {
+ c.close();
+ }
+ return isSet;
+ }
+
+ public static String getProfileName(Context context) {
+ Cursor c = context.getContentResolver().query(
+ Profile.CONTENT_URI, new String[] { Profile.DISPLAY_NAME}, null,
+ null, null);
+ String ownerName =null;
+ if (c!= null && c.moveToFirst()) {
+ ownerName = c.getString(0);
+ }
+ if (c != null) {
+ c.close();
+ }
+ return ownerName;
+ }
+ public static final String createProfileVCard(Context ctx, final int vcardType,final byte[] filter) {
+ VCardComposer composer = null;
+ String vcard = null;
+ try {
+ composer = createFilteredVCardComposer(ctx, vcardType, filter);
+ if (composer
+ .init(Profile.CONTENT_URI, null, null, null, null, Uri
+ .withAppendedPath(Profile.CONTENT_URI,
+ RawContactsEntity.CONTENT_URI
+ .getLastPathSegment()))) {
+ vcard = composer.createOneEntry();
+ } else {
+ Log.e(TAG,
+ "Unable to create profile vcard. Error initializing composer: "
+ + composer.getErrorReason());
+ }
+ } catch (Throwable t) {
+ Log.e(TAG, "Unable to create profile vcard.", t);
+ }
+ if (composer != null) {
+ try {
+ composer.terminate();
+ } catch (Throwable t) {
+
+ }
+ }
+ return vcard;
+ }
+
+ public static boolean createProfileVCardFile(File file, Context context) {
+ // File defaultFile = new
+ // File(OppApplicationConfig.OPP_OWNER_VCARD_PATH);
+ FileInputStream is = null;
+ FileOutputStream os = null;
+ boolean success = true;
+ try {
+ AssetFileDescriptor fd = context.getContentResolver()
+ .openAssetFileDescriptor(Profile.CONTENT_VCARD_URI, "r");
+
+ if(fd == null)
+ {
+ return false;
+ }
+ is = fd.createInputStream();
+ os = new FileOutputStream(file);
+ Utils.copyStream(is, os, 200);
+ } catch (Throwable t) {
+ Log.e(TAG, "Unable to create default contact vcard file", t);
+ success = false;
+ }
+ Utils.safeCloseStream(is);
+ Utils.safeCloseStream(os);
+ return success;
+ }
+}
/*
* Copyright (c) 2008-2009, Motorola, Inc.
+ * Copyright (C) 2009-2012, Broadcom Corporation
*
* All rights reserved.
*
import javax.obex.Operation;
import javax.obex.ResponseCodes;
+import com.android.bluetooth.Utils;
+
public class BluetoothPbapVcardManager {
private static final String TAG = "BluetoothPbapVcardManager";
mResolver = mContext.getContentResolver();
}
- public final String getOwnerPhoneNumberVcard(final boolean vcardType21) {
+ /**
+ * Create an owner vcard from the configured profile
+ * @param vcardType21
+ * @return
+ */
+ private final String getOwnerPhoneNumberVcardFromProfile(final boolean vcardType21, final byte[] filter) {
+ // Currently only support Generic Vcard 2.1 and 3.0
+ int vcardType;
+ if (vcardType21) {
+ vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC;
+ } else {
+ vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC;
+ }
+ return BluetoothPbapUtils.createProfileVCard(mContext, vcardType,filter);
+ }
+
+ public final String getOwnerPhoneNumberVcard(final boolean vcardType21, final byte[] filter) {
+ //Owner vCard enhancement: Use "ME" profile if configured
+ if (BluetoothPbapConfig.useProfileForOwnerVcard()) {
+ String vcard = getOwnerPhoneNumberVcardFromProfile(vcardType21, filter);
+ if (vcard != null && vcard.length() != 0) {
+ return vcard;
+ }
+ }
+ //End enhancement
+
BluetoothPbapCallLogComposer composer = new BluetoothPbapCallLogComposer(mContext);
String name = BluetoothPbapService.getLocalPhoneName();
String number = BluetoothPbapService.getLocalPhoneNum();
size = getCallHistorySize(type);
break;
}
- if (V) Log.v(TAG, "getPhonebookSzie size = " + size + " type = " + type);
+ if (V) Log.v(TAG, "getPhonebookSize size = " + size + " type = " + type);
return size;
}
public final ArrayList<String> getPhonebookNameList(final int orderByWhat) {
ArrayList<String> nameList = new ArrayList<String>();
- nameList.add(BluetoothPbapService.getLocalPhoneName());
+ //Owner vCard enhancement. Use "ME" profile if configured
+ String ownerName = null;
+ if (BluetoothPbapConfig.useProfileForOwnerVcard()) {
+ ownerName = BluetoothPbapUtils.getProfileName(mContext);
+ }
+ if (ownerName == null || ownerName.length()==0) {
+ ownerName = BluetoothPbapService.getLocalPhoneName();
+ }
+ nameList.add(ownerName);
+ //End enhancement
final Uri myUri = Contacts.CONTENT_URI;
Cursor contactCursor = null;
}
vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT;
- composer = new VCardComposer(mContext, vcardType, true);
+ //Enhancement: customize Vcard based on preferences/settings and input from caller
+ composer = BluetoothPbapUtils.createFilteredVCardComposer(mContext, vcardType,null);
+ //End enhancement
+
// BT does want PAUSE/WAIT conversion while it doesn't want the other formatting
// done by vCard library by default.
composer.setPhoneNumberTranslationCallback(
+ composer.getErrorReason());
return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
}
+ if (V) {
+ Log.v(TAG, "Vcard Entry:");
+ Log.v(TAG,vcard);
+ }
+
if (!buffer.onEntryCreated(vcard)) {
// onEntryCreate() already emits error.
return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ composer.getErrorReason());
return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
}
+ if (V) {
+ Log.v(TAG, "Vcard Entry:");
+ Log.v(TAG,vcard);
+ }
+
buffer.onEntryCreated(vcard);
}
} finally {