Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7fdd913c authored by Andrew Cheng's avatar Andrew Cheng
Browse files

Switch VCardParser version if it doesn't match vCard version

If we get a VCardVersionException (i.e., our VCardParser's version
doesn't match the vCard's version), try changing our VCardParser to the
other version (either v2.1 or v3.0), and try parsing again.

Currently, parsing simply fails upon VCardVersionException, so none of
the contacts and call history are downloaded.

Bug: 311404211
Test: atest BluetoothPbapVcardListTest
Test: atest BluetoothPbapRequestPullPhoneBookTest
Test: atest PbapClientConnectionHandlerTest
Flag: EXEMPT, code path is not executed on phones, ramping may be close
to non-existent and possibly not enough users to pass the gantry gate,
unit tests were added.

Change-Id: I005060a57b6d5c12049a717ea9c6c6041192e694
parent c5aa82b5
Loading
Loading
Loading
Loading
+56 −6
Original line number Diff line number Diff line
@@ -17,22 +17,27 @@
package com.android.bluetooth.pbapclient;

import android.accounts.Account;
import android.util.Log;

import com.android.vcard.VCardConfig;
import com.android.vcard.VCardEntry;
import com.android.vcard.VCardEntryConstructor;
import com.android.vcard.VCardEntryCounter;
import com.android.vcard.VCardEntryHandler;
import com.android.vcard.VCardParser;
import com.android.vcard.VCardParser_V21;
import com.android.vcard.VCardParser_V30;
import com.android.vcard.exception.VCardException;
import com.android.vcard.exception.VCardVersionException;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

class BluetoothPbapVcardList {
    private static final String TAG = BluetoothPbapVcardList.class.getSimpleName();
    // {@link BufferedInputStream#DEFAULT_BUFFER_SIZE} is not public
    private static final int BIS_DEFAULT_BUFFER_SIZE = 8192;

    private final ArrayList<VCardEntry> mCards = new ArrayList<VCardEntry>();
    private final Account mAccount;
@@ -53,6 +58,10 @@ class BluetoothPbapVcardList {
    }

    BluetoothPbapVcardList(Account account, InputStream in, byte format) throws IOException {
        if (format != PbapClientConnectionHandler.VCARD_TYPE_21
                && format != PbapClientConnectionHandler.VCARD_TYPE_30) {
            throw new IllegalArgumentException("Unsupported vCard version.");
        }
        mAccount = account;
        parse(in, format);
    }
@@ -68,19 +77,60 @@ class BluetoothPbapVcardList {

        VCardEntryConstructor constructor =
                new VCardEntryConstructor(VCardConfig.VCARD_TYPE_V21_GENERIC, mAccount);
        VCardEntryCounter counter = new VCardEntryCounter();
        CardEntryHandler handler = new CardEntryHandler();

        CardEntryHandler handler = new CardEntryHandler();
        constructor.addEntryHandler(handler);

        parser.addInterpreter(constructor);
        parser.addInterpreter(counter);

        // {@link BufferedInputStream} supports the {@link InputStream#mark} and
        // {@link InputStream#reset} methods.
        BufferedInputStream bufferedInput = new BufferedInputStream(in);
        bufferedInput.mark(BIS_DEFAULT_BUFFER_SIZE /* readlimit */);

        // If there is a {@link VCardVersionException}, try parsing again with a different
        // version. Otherwise, parsing either succeeds (i.e., no {@link VCardException}) or it
        // fails with a different {@link VCardException}.
        if (parsedWithVcardVersionException(parser, bufferedInput)) {
            // PBAP v1.2.3 only supports vCard versions 2.1 and 3.0; it's one or the other
            if (format == PbapClientConnectionHandler.VCARD_TYPE_21) {
                parser = new VCardParser_V30();
                Log.w(TAG, "vCard version and Parser mismatch; expected v2.1, switching to v3.0");
            } else {
                parser = new VCardParser_V21();
                Log.w(TAG, "vCard version and Parser mismatch; expected v3.0, switching to v2.1");
            }
            // reset and try again
            bufferedInput.reset();
            mCards.clear();
            constructor.clear();
            parser.addInterpreter(constructor);
            if (parsedWithVcardVersionException(parser, bufferedInput)) {
                Log.e(TAG, "unsupported vCard version, neither v2.1 nor v3.0");
            }
        }
    }

    /**
     * Attempts to parse, with an eye on whether the correct version of Parser is used.
     *
     * @param parser -- the {@link VCardParser} to use.
     * @param in -- the {@link InputStream} to parse.
     * @return {@code true} if there was a {@link VCardVersionException}; {@code false} if there
     *         is any other {@link VCardException} or succeeds (i.e., no {@link VCardException}).
     * @throws IOException if there's an issue reading the {@link InputStream}.
     */
    private boolean parsedWithVcardVersionException(VCardParser parser, InputStream in)
            throws IOException {
        try {
            parser.parse(in);
        } catch (VCardException e) {
            e.printStackTrace();
        } catch (VCardVersionException e1) {
            Log.w(TAG, "vCard version and Parser mismatch", e1);
            return true;
        } catch (VCardException e2) {
            Log.e(TAG, "vCard exception", e2);
        }
        return false;
    }

    public int getCount() {
+6 −2
Original line number Diff line number Diff line
@@ -435,7 +435,9 @@ class PbapClientConnectionHandler extends Handler {
                Log.w(TAG, "Download contacts incomplete, index exceeded upper limit.");
            }
        } catch (IOException e) {
            Log.w(TAG, "Download contacts failure" + e.toString());
            Log.e(TAG, "Download contacts failure", e);
        } catch (IllegalArgumentException e) {
            Log.e(TAG, "Download contacts failure: " + e.getMessage(), e);
        }
    }

@@ -451,7 +453,9 @@ class PbapClientConnectionHandler extends Handler {
            processor.setResults(request.getList());
            processor.onPullComplete();
        } catch (IOException e) {
            Log.w(TAG, "Download call log failure");
            Log.e(TAG, "Download call log failure", e);
        } catch (IllegalArgumentException e) {
            Log.e(TAG, "Download call log failure: " + e.getMessage(), e);
        }
    }

+10 −0
Original line number Diff line number Diff line
BEGIN:VCARD
VERSION:googol
FN:Jean Dupont
N:Dupont;Jean
ADR;WORK;QUOTED-PRINTABLE:;Paris 75010;91 Rue du Faubourg Saint-
Martin
TEL;CELL;PREF:+1234 56789
EMAIL;INTERNET:jean.dupont@example.com
X-BT-UID:A1A2A3A4B1B2C1C2D1D2E1E2E3E4E5E6
END:VCARD
+10 −0
Original line number Diff line number Diff line
BEGIN:VCARD
VERSION:2.1
FN:Jean Dupont
N:Dupont;Jean
ADR;WORK;QUOTED-PRINTABLE:;Paris 75010;91 Rue du Faubourg Saint-
Martin
TEL;CELL;PREF:+1234 56789
EMAIL;INTERNET:jean.dupont@example.com
X-BT-UID:A1A2A3A4B1B2C1C2D1D2E1E2E3E4E5E6
END:VCARD
+18 −2
Original line number Diff line number Diff line
@@ -69,7 +69,7 @@ public class BluetoothPbapRequestPullPhoneBookTest {
    }

    @Test
    public void readResponse_failWithMockInputStream() {
    public void readResponse_failWithInputStreamThatThrowsIOEWhenRead() {
        final long filter = 1;
        final byte format = 0; // Will be properly handled as VCARD_TYPE_21.
        final int maxListCount = 0; // Will be specially handled as 65535.
@@ -77,7 +77,23 @@ public class BluetoothPbapRequestPullPhoneBookTest {
        BluetoothPbapRequestPullPhoneBook request = new BluetoothPbapRequestPullPhoneBook(
                PB_NAME, ACCOUNT, filter, format, maxListCount, listStartOffset);

        InputStream is = mock(InputStream.class);
        final InputStream is = new InputStream() {
            @Override
            public int read() throws IOException {
                throw new IOException();
            }

            @Override
            public int read(byte[] b) throws IOException {
                throw new IOException();
            }

            @Override
            public int read(byte[] b, int off, int len) throws IOException {
                throw new IOException();
            }
        };

        assertThrows(IOException.class, () -> request.readResponse(is));
    }

Loading