Merge "PBAP server, send favorite contacts"
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
index 306f31d..be161bb 100755
--- a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
@@ -97,6 +97,7 @@
     private static final String[] LEGAL_PATH = {
             "/telecom",
             "/telecom/pb",
+            "/telecom/fav",
             "/telecom/ich",
             "/telecom/och",
             "/telecom/mch",
@@ -106,6 +107,7 @@
     @SuppressWarnings("unused") private static final String[] LEGAL_PATH_WITH_SIM = {
             "/telecom",
             "/telecom/pb",
+            "/telecom/fav",
             "/telecom/ich",
             "/telecom/och",
             "/telecom/mch",
@@ -138,6 +140,9 @@
     // phone book
     private static final String PB = "pb";
 
+    // favorites
+    private static final String FAV = "fav";
+
     private static final String TELECOM_PATH = "/telecom";
 
     private static final String ICH_PATH = "/telecom/ich";
@@ -150,6 +155,8 @@
 
     private static final String PB_PATH = "/telecom/pb";
 
+    private static final String FAV_PATH = "/telecom/fav";
+
     // type for list vcard objects
     private static final String TYPE_LISTING = "x-bt/vcard-listing";
 
@@ -212,6 +219,8 @@
         public static final int MISSED_CALL_HISTORY = 4;
 
         public static final int COMBINED_CALL_HISTORY = 5;
+
+        public static final int FAVORITES = 6;
     }
 
     public BluetoothPbapObexServer(Handler callback, Context context,
@@ -441,6 +450,8 @@
 
             if (mCurrentPath.equals(PB_PATH)) {
                 appParamValue.needTag = ContentType.PHONEBOOK;
+            } else if (mCurrentPath.equals(FAV_PATH)) {
+                appParamValue.needTag = ContentType.FAVORITES;
             } else if (mCurrentPath.equals(ICH_PATH)) {
                 appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY;
             } else if (mCurrentPath.equals(OCH_PATH)) {
@@ -478,6 +489,11 @@
                 if (D) {
                     Log.v(TAG, "download phonebook request");
                 }
+            } else if (isNameMatchTarget(name, FAV)) {
+                appParamValue.needTag = ContentType.FAVORITES;
+                if (D) {
+                    Log.v(TAG, "download favorites request");
+                }
             } else if (isNameMatchTarget(name, ICH)) {
                 appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY;
                 appParamValue.callHistoryVersionCounter =
@@ -751,7 +767,8 @@
         result.append("<vCard-listing version=\"1.0\">");
 
         // Phonebook listing request
-        if (appParamValue.needTag == ContentType.PHONEBOOK) {
+        if ((appParamValue.needTag == ContentType.PHONEBOOK)
+                || (appParamValue.needTag == ContentType.FAVORITES)) {
             String type = "";
             if (appParamValue.searchAttr.equals("0")) {
                 type = "name";
@@ -948,7 +965,7 @@
                     checkPbapFeatureSupport(mFolderVersionCounterbitMask);
         }
         boolean needSendPhonebookVersionCounters = false;
-        if (isNameMatchTarget(name, PB)) {
+        if (isNameMatchTarget(name, PB) || isNameMatchTarget(name, FAV)) {
             needSendPhonebookVersionCounters =
                     checkPbapFeatureSupport(mFolderVersionCounterbitMask);
         }
@@ -1194,11 +1211,12 @@
         if (appParamValue.needTag == 0) {
             Log.w(TAG, "wrong path!");
             return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
-        } else if (appParamValue.needTag == ContentType.PHONEBOOK) {
+        } else if ((appParamValue.needTag == ContentType.PHONEBOOK)
+                || (appParamValue.needTag == ContentType.FAVORITES)) {
             if (intIndex < 0 || intIndex >= size) {
                 Log.w(TAG, "The requested vcard is not acceptable! name= " + name);
                 return ResponseCodes.OBEX_HTTP_NOT_FOUND;
-            } else if (intIndex == 0) {
+            } else if ((intIndex == 0) && (appParamValue.needTag == ContentType.PHONEBOOK)) {
                 // For PB_PATH, 0.vcf is the phone number of this phone.
                 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21,
                         appParamValue.ignorefilter ? null : appParamValue.propertySelector);
@@ -1254,30 +1272,49 @@
 
         int requestSize =
                 pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount : pbSize;
-        int startPoint = appParamValue.listStartOffset;
-        if (startPoint < 0 || startPoint >= pbSize) {
+        /**
+         * startIndex (resp., lastIndex) corresponds to the index of the first (resp., last)
+         * vcard entry in the phonebook object.
+         * PBAP v1.2.3: only pb starts indexing at 0.vcf (owner card), the other phonebook
+         * objects (e.g., fav) start at 1.vcf. Additionally, the owner card is included in
+         * pb's pbSize. This means pbSize corresponds to the index of the last vcf in the fav
+         * phonebook object, but does not for the pb phonebook object.
+         */
+        int startIndex = 1;
+        int lastIndex = pbSize;
+        if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) {
+            startIndex = 0;
+            lastIndex = pbSize - 1;
+        }
+        // [startPoint, endPoint] denote the range of vcf indices to send, inclusive.
+        int startPoint = startIndex + appParamValue.listStartOffset;
+        int endPoint = startPoint + requestSize - 1;
+        if (appParamValue.listStartOffset < 0 || startPoint > lastIndex) {
             Log.w(TAG, "listStartOffset is not correct! " + startPoint);
             return ResponseCodes.OBEX_HTTP_OK;
         }
+        if (endPoint > lastIndex) {
+            endPoint = lastIndex;
+        }
 
         // Limit the number of call log to CALLLOG_NUM_LIMIT
-        if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) {
+        if ((appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK)
+                && (appParamValue.needTag != BluetoothPbapObexServer.ContentType.FAVORITES)) {
             if (requestSize > CALLLOG_NUM_LIMIT) {
                 requestSize = CALLLOG_NUM_LIMIT;
             }
         }
 
-        int endPoint = startPoint + requestSize - 1;
-        if (endPoint > pbSize - 1) {
-            endPoint = pbSize - 1;
-        }
         if (D) {
             Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" + startPoint
                     + " endPoint=" + endPoint);
         }
 
         boolean vcard21 = appParamValue.vcard21;
-        if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) {
+        boolean favorites =
+                (appParamValue.needTag == BluetoothPbapObexServer.ContentType.FAVORITES);
+        if ((appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK)
+                || favorites) {
             if (startPoint == 0) {
                 String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21,
                         appParamValue.ignorefilter ? null : appParamValue.propertySelector);
@@ -1287,13 +1324,13 @@
                     return mVcardManager.composeAndSendPhonebookVcards(op, 1, endPoint, vcard21,
                             ownerVcard, needSendBody, pbSize, appParamValue.ignorefilter,
                             appParamValue.propertySelector, appParamValue.vCardSelector,
-                            appParamValue.vCardSelectorOperator, mVcardSelector);
+                            appParamValue.vCardSelectorOperator, mVcardSelector, favorites);
                 }
             } else {
                 return mVcardManager.composeAndSendPhonebookVcards(op, startPoint, endPoint,
                         vcard21, null, needSendBody, pbSize, appParamValue.ignorefilter,
                         appParamValue.propertySelector, appParamValue.vCardSelector,
-                        appParamValue.vCardSelectorOperator, mVcardSelector);
+                        appParamValue.vCardSelectorOperator, mVcardSelector, favorites);
             }
         } else {
             return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op,
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
index 6a13af3..04680fa 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapService.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
@@ -139,7 +139,8 @@
     private ObexServerSockets mServerSockets = null;
 
     private static final int SDP_PBAP_SERVER_VERSION = 0x0102;
-    private static final int SDP_PBAP_SUPPORTED_REPOSITORIES = 0x0001;
+    // PBAP v1.2.3, Sec. 7.1.2: local phonebook and favorites
+    private static final int SDP_PBAP_SUPPORTED_REPOSITORIES = 0x0009;
     private static final int SDP_PBAP_SUPPORTED_FEATURES = 0x021F;
 
     /* PBAP will use Bluetooth notification ID from 1000000 (included) to 2000000 (excluded).
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
index 5ba2b4b..8801c16 100755
--- a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
@@ -153,7 +153,8 @@
         int size;
         switch (type) {
             case BluetoothPbapObexServer.ContentType.PHONEBOOK:
-                size = getContactsSize();
+            case BluetoothPbapObexServer.ContentType.FAVORITES:
+                size = getContactsSize(type);
                 break;
             default:
                 size = getCallHistorySize(type);
@@ -165,16 +166,30 @@
         return size;
     }
 
-    public final int getContactsSize() {
+    /**
+     * Returns the number of contacts (i.e., vcf) in a phonebook object.
+     * @param type specifies which phonebook object, e.g., pb, fav
+     * @return
+     */
+    public final int getContactsSize(final int type) {
         final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
         Cursor contactCursor = null;
+        String selectionClause = null;
+        if (type == BluetoothPbapObexServer.ContentType.FAVORITES) {
+            selectionClause = Phone.STARRED + " = 1";
+        }
         try {
-            contactCursor = mResolver.query(myUri, new String[]{Phone.CONTACT_ID}, null, null,
-                    Phone.CONTACT_ID);
+            contactCursor = mResolver.query(myUri,
+                    new String[]{Phone.CONTACT_ID}, selectionClause,
+                    null, Phone.CONTACT_ID);
             if (contactCursor == null) {
                 return 0;
             }
-            return getDistinctContactIdSize(contactCursor) + 1; // always has the 0.vcf
+            int contactsSize = getDistinctContactIdSize(contactCursor);
+            if (type == BluetoothPbapObexServer.ContentType.PHONEBOOK) {
+                contactsSize += 1; // pb has the 0.vcf owner's card
+            }
+            return contactsSize;
         } catch (CursorWindowAllocationException e) {
             Log.e(TAG, "CursorWindowAllocationException while getting Contacts size");
         } finally {
@@ -551,7 +566,7 @@
     final int composeAndSendPhonebookVcards(Operation op, final int startPoint, final int endPoint,
             final boolean vcardType21, String ownerVCard, int needSendBody, int pbSize,
             boolean ignorefilter, byte[] filter, byte[] vcardselector, String vcardselectorop,
-            boolean vcardselect) {
+            boolean vcardselect, boolean favorites) {
         if (startPoint < 1 || startPoint > endPoint) {
             Log.e(TAG, "internal error: startPoint or endPoint is not correct.");
             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
@@ -562,9 +577,15 @@
         Cursor contactIdCursor = new MatrixCursor(new String[]{
                 Phone.CONTACT_ID
         });
+
+        String selectionClause = null;
+        if (favorites) {
+            selectionClause = Phone.STARRED + " = 1";
+        }
+
         try {
-            contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null,
-                    Phone.CONTACT_ID);
+            contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, selectionClause,
+                    null, Phone.CONTACT_ID);
             if (contactCursor != null) {
                 contactIdCursor =
                         ContactCursorFilter.filterByRange(contactCursor, startPoint, endPoint);