OSDN Git Service

Added PBAP support for fetching owner vcard info from phone's profile. Added PBAP...
authorfredc <fredc@broadcom.com>
Mon, 7 May 2012 21:20:35 +0000 (14:20 -0700)
committerAndroid (Google) Code Review <android-gerrit@google.com>
Tue, 17 Jul 2012 05:06:13 +0000 (22:06 -0700)
Change-Id: Ieb2993c9505185d4f85cbbf6b5c554936618cff0

AndroidManifest.xml
res/values/config.xml [new file with mode: 0644]
src/com/android/bluetooth/Utils.java
src/com/android/bluetooth/pbap/BluetoothPbapConfig.java [new file with mode: 0644]
src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
src/com/android/bluetooth/pbap/BluetoothPbapService.java
src/com/android/bluetooth/pbap/BluetoothPbapUtils.java [new file with mode: 0644]
src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java

index a104eeb..698c35b 100644 (file)
@@ -42,6 +42,8 @@
     <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"
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644 (file)
index 0000000..538325e
--- /dev/null
@@ -0,0 +1,18 @@
+<?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
index 7ace4cd..6e0e23a 100644 (file)
@@ -7,7 +7,11 @@ package com.android.bluetooth;
 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;
@@ -17,6 +21,7 @@ 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
 
@@ -127,4 +132,34 @@ final public class Utils {
             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);
+            }
+        }
+    }
 }
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapConfig.java b/src/com/android/bluetooth/pbap/BluetoothPbapConfig.java
new file mode 100644 (file)
index 0000000..30c0254
--- /dev/null
@@ -0,0 +1,59 @@
+/************************************************************************************
+ *
+ *  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;
+    }
+}
index 6002263..a0ebfe9 100755 (executable)
@@ -849,7 +849,7 @@ public class BluetoothPbapObexServer extends ServerRequestHandler {
                 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,
@@ -920,7 +920,7 @@ public class BluetoothPbapObexServer extends ServerRequestHandler {
         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 {
index 982a322..6ff0bf5 100755 (executable)
@@ -196,7 +196,7 @@ public class BluetoothPbapService extends Service {
         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
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java b/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
new file mode 100644 (file)
index 0000000..1aae276
--- /dev/null
@@ -0,0 +1,207 @@
+/************************************************************************************
+ *
+ *  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;
+    }
+}
index 659add6..37156fa 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2008-2009, Motorola, Inc.
+ * Copyright (C) 2009-2012, Broadcom Corporation
  *
  * All rights reserved.
  *
@@ -61,6 +62,8 @@ import javax.obex.ServerOperation;
 import javax.obex.Operation;
 import javax.obex.ResponseCodes;
 
+import com.android.bluetooth.Utils;
+
 public class BluetoothPbapVcardManager {
     private static final String TAG = "BluetoothPbapVcardManager";
 
@@ -104,7 +107,32 @@ public class 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();
@@ -123,7 +151,7 @@ public class BluetoothPbapVcardManager {
                 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;
     }
 
@@ -203,7 +231,16 @@ public class BluetoothPbapVcardManager {
 
     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;
@@ -445,7 +482,10 @@ public class BluetoothPbapVcardManager {
                 }
                 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(
@@ -479,6 +519,11 @@ public class BluetoothPbapVcardManager {
                                 + 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;
@@ -517,6 +562,11 @@ public class BluetoothPbapVcardManager {
                                 + composer.getErrorReason());
                         return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
                     }
+                    if (V) {
+                        Log.v(TAG, "Vcard Entry:");
+                        Log.v(TAG,vcard);
+                    }
+
                     buffer.onEntryCreated(vcard);
                 }
             } finally {