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

Commit ba2b21be authored by Daisuke Miyakawa's avatar Daisuke Miyakawa
Browse files

Implement unit tests for vCard exporter, which depends on the sucess in vCard importer.

In order to share the logic between tests for importer and those for exporter,
PropertyNodesVerifier is now a separated class and drastically modified.
Now the class accept "unordered" expected PropertyNode objects, which allows vCard
composer to not care the exact order of each elements.

MockCursor is added, which may be added into the public API in the future, but in
the test directory for now.
Another MockContentProvider is (temporarily) developed so that it can be accepted by
MockContentResolver#addProvider(), which does not allow IContentProvider and its
descendants but only exact ContentProvider, while the original MockContentProvider in
android.test.mock.MockContentProvider implements IContentProvider.

The test development is still on-going, but this test suffices minimal requirement of
vCard tests.

Internal issue number: 2160039
parent 3e88ddc5
Loading
Loading
Loading
Loading
+46 −23
Original line number Diff line number Diff line
@@ -111,6 +111,10 @@ public class VCardComposer {
    public static final String FAILURE_REASON_NOT_INITIALIZED =
        "The vCard composer object is not correctly initialized";

    /** Should be visible only from developers... (no need to translate, hopefully) */
    public static final String FAILURE_REASON_UNSUPPORTED_URI =
        "The Uri vCard composer received is not supported by the composer.";

    public static final String NO_ERROR = "No error";

    public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
@@ -138,6 +142,15 @@ public class VCardComposer {

    private static final String SHIFT_JIS = "SHIFT_JIS";

    /**
     * Special URI for testing.
     */
    public static final String VCARD_TEST_AUTHORITY = "com.android.unit_tests.vcard";
    public static final Uri VCARD_TEST_AUTHORITY_URI =
        Uri.parse("content://" + VCARD_TEST_AUTHORITY);
    public static final Uri CONTACTS_TEST_CONTENT_URI =
        Uri.withAppendedPath(VCARD_TEST_AUTHORITY_URI, "contacts");

    private static final Uri sDataRequestUri;
    private static final Map<Integer, String> sImMap;

@@ -286,7 +299,7 @@ public class VCardComposer {

    private String mErrorReason = NO_ERROR;

    private boolean mIsCallLogComposer = false;
    private boolean mIsCallLogComposer;

    private static final String[] sContactsProjection = new String[] {
        Contacts._ID,
@@ -307,30 +320,24 @@ public class VCardComposer {
    private static final String FLAG_TIMEZONE_UTC = "Z";

    public VCardComposer(Context context) {
        this(context, VCardConfig.VCARD_TYPE_DEFAULT, true, false);
        this(context, VCardConfig.VCARD_TYPE_DEFAULT, true);
    }

    public VCardComposer(Context context, String vcardTypeStr,
            boolean careHandlerErrors) {
        this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr),
                careHandlerErrors, false);
    public VCardComposer(Context context, int vcardType) {
        this(context, vcardType, true);
    }

    public VCardComposer(Context context, int vcardType, boolean careHandlerErrors) {
        this(context, vcardType, careHandlerErrors, false);
    public VCardComposer(Context context, String vcardTypeStr, boolean careHandlerErrors) {
        this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr), careHandlerErrors);
    }

    /**
     * Construct for supporting call log entry vCard composing.
     *
     * @param isCallLogComposer true if this composer is for creating Call Log vCard.
     */
    public VCardComposer(Context context, int vcardType, boolean careHandlerErrors,
            boolean isCallLogComposer) {
    public VCardComposer(Context context, int vcardType, boolean careHandlerErrors) {
        mContext = context;
        mVCardType = vcardType;
        mCareHandlerErrors = careHandlerErrors;
        mIsCallLogComposer = isCallLogComposer;
        mContentResolver = context.getContentResolver();

        mIsV30 = VCardConfig.isV30(vcardType);
@@ -370,15 +377,26 @@ public class VCardComposer {
        mHandlerList.add(handler);
    }

    /**
     * @return Returns true when initialization is successful and all the other
     *          methods are available. Returns false otherwise.
     */
    public boolean init() {
        return init(null, null);
    }

    public boolean init(final String selection, final String[] selectionArgs) {
        return init(Contacts.CONTENT_URI, selection, selectionArgs, null);
    }

    /**
     * @return Returns true when initialization is successful and all the other
     *          methods are available. Returns false otherwise.
     * Note that this is unstable interface, may be deleted in the future.
     */
    public boolean init(final String selection, final String[] selectionArgs) {
    public boolean init(final Uri contentUri, final String selection,
            final String[] selectionArgs, final String sortOrder) {
        if (contentUri == null) {
            return false;
        }
        if (mCareHandlerErrors) {
            List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
                    mHandlerList.size());
@@ -397,13 +415,19 @@ public class VCardComposer {
            }
        }

        if (mIsCallLogComposer) {
            mCursor = mContentResolver.query(CallLog.Calls.CONTENT_URI, sCallLogProjection,
                    selection, selectionArgs, null);
        final String[] projection;
        if (CallLog.Calls.CONTENT_URI.equals(contentUri)) {
            projection = sCallLogProjection;
            mIsCallLogComposer = true;
        } else if (Contacts.CONTENT_URI.equals(contentUri) ||
                CONTACTS_TEST_CONTENT_URI.equals(contentUri)) {
            projection = sContactsProjection;
        } else {
            mCursor = mContentResolver.query(Contacts.CONTENT_URI, sContactsProjection,
                    selection, selectionArgs, null);
            mErrorReason = FAILURE_REASON_UNSUPPORTED_URI;
            return false;
        }
        mCursor = mContentResolver.query(
                contentUri, projection, selection, selectionArgs, sortOrder);

        if (mCursor == null) {
            mErrorReason = FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO;
@@ -496,8 +520,7 @@ public class VCardComposer {
            dataExists = entityIterator.hasNext();
            while (entityIterator.hasNext()) {
                Entity entity = entityIterator.next();
                for (NamedContentValues namedContentValues : entity
                        .getSubValues()) {
                for (NamedContentValues namedContentValues : entity.getSubValues()) {
                    ContentValues contentValues = namedContentValues.values;
                    String key = contentValues.getAsString(Data.MIMETYPE);
                    if (key != null) {
+12 −1
Original line number Diff line number Diff line
@@ -525,10 +525,18 @@ public class VCardParser_V21 extends VCardParser {
                throw new VCardException("Unknown type \"" + paramName + "\"");
            }
        } else {
            handleType(strArray[0]);
            handleParamWithoutName(strArray[0]);
        }
    }
    
    /**
     * vCard 3.0 parser may throw VCardException.
     */
    @SuppressWarnings("unused")
    protected void handleParamWithoutName(final String paramValue) throws VCardException {
        handleType(paramValue);
    }

    /**
     * ptypeval = knowntype / "X-" word
     */
@@ -832,6 +840,9 @@ public class VCardParser_V21 extends VCardParser {
    @Override
    public boolean parse(InputStream is, String charset, VCardBuilder builder)
            throws IOException, VCardException {
        if (charset == null) {
            charset = VCardConfig.DEFAULT_CHARSET;
        }
        final InputStreamReader tmpReader = new InputStreamReader(is, charset);
        if (VCardConfig.showPerformanceLog()) {
            mReader = new CustomBufferedReader(tmpReader);
+34 −2
Original line number Diff line number Diff line
@@ -49,6 +49,29 @@ public class VCardParser_V30 extends VCardParser_V21 {

    private boolean mEmittedAgentWarning = false;

    /**
     * True when the caller wants the parser to be strict about the input.
     * Currently this is only for testing.
     */
    private final boolean mStrictParsing;

    public VCardParser_V30() {
        super();
        mStrictParsing = false;
    }

    /**
     * @param strictParsing when true, this object throws VCardException when the vcard is not
     * valid from the view of vCard 3.0 specification (defined in RFC 2426). Note that this class
     * is not fully yet for being used with this flag and may not notice invalid line(s).
     *
     * @hide currently only for testing! 
     */
    public VCardParser_V30(boolean strictParsing) {
        super();
        mStrictParsing = strictParsing;
    }

    @Override
    protected int getVersion() {
        return VCardConfig.FLAG_V30;
@@ -205,6 +228,15 @@ public class VCardParser_V30 extends VCardParser_V21 {
        super.handleAnyParam(paramName, paramValue);
    }

    @Override
    protected void handleParamWithoutName(final String paramValue) throws VCardException {
        if (mStrictParsing) {
            throw new VCardException("Parameter without name is not acceptable in vCard 3.0");
        } else {
            super.handleParamWithoutName(paramValue);
        }
    }

    /**
     *  vCard 3.0 defines
     *  
+254 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * 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.unit_tests.vcard;

import android.content.ContentResolver;
import android.database.CharArrayBuffer;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Bundle;

import java.util.Map;

public class MockCursor implements Cursor {
    public int getColumnCount() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public int getColumnIndex(String columnName) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public int getColumnIndexOrThrow(String columnName) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public String getColumnName(int columnIndex) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public String[] getColumnNames() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public int getCount() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public boolean isNull(int columnIndex) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public int getInt(int columnIndex) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public long getLong(int columnIndex) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public short getShort(int columnIndex) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public float getFloat(int columnIndex) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public double getDouble(int columnIndex) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public byte[] getBlob(int columnIndex) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public String getString(int columnIndex) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public Bundle getExtras() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public int getPosition() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public boolean isAfterLast() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public boolean isBeforeFirst() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public boolean isFirst() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public boolean isLast() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public boolean move(int offset) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public boolean moveToFirst() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public boolean moveToLast() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public boolean moveToNext() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public boolean moveToPrevious() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public boolean moveToPosition(int position) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public void deactivate() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public void close() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public boolean isClosed() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public boolean requery() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public void registerContentObserver(ContentObserver observer) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public void registerDataSetObserver(DataSetObserver observer) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public Bundle respond(Bundle extras) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    public boolean getWantsAllOnMoveCalls() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    @SuppressWarnings("deprecation")
    public boolean commitUpdates() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    @SuppressWarnings("deprecation")
    public boolean commitUpdates(Map<? extends Long, ? extends Map<String, Object>> values) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    @SuppressWarnings("deprecation")
    public boolean hasUpdates() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    @SuppressWarnings("deprecation")
    public void setNotificationUri(ContentResolver cr, Uri uri) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    @SuppressWarnings("deprecation")
    public boolean supportsUpdates() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    @SuppressWarnings("deprecation")
    public boolean deleteRow() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    @SuppressWarnings("deprecation")
    public void unregisterContentObserver(ContentObserver observer) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    @SuppressWarnings("deprecation")
    public void unregisterDataSetObserver(DataSetObserver observer) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    @SuppressWarnings("deprecation")
    public boolean updateBlob(int columnIndex, byte[] value) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    @SuppressWarnings("deprecation")
    public boolean updateDouble(int columnIndex, double value) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    @SuppressWarnings("deprecation")
    public boolean updateFloat(int columnIndex, float value) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    @SuppressWarnings("deprecation")
    public boolean updateInt(int columnIndex, int value) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    @SuppressWarnings("deprecation")
    public boolean updateLong(int columnIndex, long value) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    @SuppressWarnings("deprecation")
    public boolean updateShort(int columnIndex, short value) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    @SuppressWarnings("deprecation")
    public boolean updateString(int columnIndex, String value) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    @SuppressWarnings("deprecation")
    public boolean updateToNull(int columnIndex) {
        throw new UnsupportedOperationException("unimplemented mock method");
    }

    @SuppressWarnings("deprecation")
    public void abortUpdates() {
        throw new UnsupportedOperationException("unimplemented mock method");
    }
}
 No newline at end of file
+9 −164
Original line number Diff line number Diff line
@@ -18,15 +18,11 @@ package com.android.unit_tests.vcard;
import android.content.ContentValues;
import android.pim.vcard.ContactStruct;

import org.apache.commons.codec.binary.Base64;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;
import java.util.regex.Pattern;

/**
 * Previously used in main vCard handling code but now exists only for testing.
@@ -106,6 +102,15 @@ public class PropertyNode {
        }
    }
    
    @Override
    public int hashCode() {
        // vCard may contain more than one same line in one entry, while HashSet or any other
        // library which utilize hashCode() does not honor that, so intentionally throw an
        // Exception.
        throw new UnsupportedOperationException(
                "PropertyNode does not provide hashCode() implementation intentionally.");
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof PropertyNode)) {
@@ -165,164 +170,4 @@ public class PropertyNode {
        builder.append(propValue);
        return builder.toString();
    }
    
    /**
     * Encode this object into a string which can be decoded. 
     */
    public String encode() {
        // PropertyNode#toString() is for reading, not for parsing in the future.
        // We construct appropriate String here.
        StringBuilder builder = new StringBuilder();
        if (propName.length() > 0) {
            builder.append("propName:[");
            builder.append(propName);
            builder.append("],");
        }
        int size = propGroupSet.size();
        if (size > 0) {
            Set<String> set = propGroupSet;
            builder.append("propGroup:[");
            int i = 0;
            for (String group : set) {
                // We do not need to double quote groups.
                // group        = 1*(ALPHA / DIGIT / "-")
                builder.append(group);
                if (i < size - 1) {
                    builder.append(",");
                }
                i++;
            }
            builder.append("],");
        }

        if (paramMap.size() > 0 || paramMap_TYPE.size() > 0) {
            ContentValues values = paramMap;
            builder.append("paramMap:[");
            size = paramMap.size(); 
            int i = 0;
            for (Entry<String, Object> entry : values.valueSet()) {
                // Assuming param-key does not contain NON-ASCII nor symbols.
                //
                // According to vCard 3.0:
                // param-name   = iana-token / x-name
                builder.append(entry.getKey());

                // param-value may contain any value including NON-ASCIIs.
                // We use the following replacing rule.
                // \ -> \\
                // , -> \,
                // In String#replaceAll(), "\\\\" means a single backslash.
                builder.append("=");
                builder.append(entry.getValue().toString()
                        .replaceAll("\\\\", "\\\\\\\\")
                        .replaceAll(",", "\\\\,"));
                if (i < size -1) {
                    builder.append(",");
                }
                i++;
            }

            Set<String> set = paramMap_TYPE;
            size = paramMap_TYPE.size();
            if (i > 0 && size > 0) {
                builder.append(",");
            }
            i = 0;
            for (String type : set) {
                builder.append("TYPE=");
                builder.append(type
                        .replaceAll("\\\\", "\\\\\\\\")
                        .replaceAll(",", "\\\\,"));
                if (i < size - 1) {
                    builder.append(",");
                }
                i++;
            }
            builder.append("],");
        }

        size = propValue_vector.size();
        if (size > 0) {
            builder.append("propValue:[");
            List<String> list = propValue_vector;
            for (int i = 0; i < size; i++) {
                builder.append(list.get(i)
                        .replaceAll("\\\\", "\\\\\\\\")
                        .replaceAll(",", "\\\\,"));
                if (i < size -1) {
                    builder.append(",");
                }
            }
            builder.append("],");
        }

        return builder.toString();
    }

    public static PropertyNode decode(String encodedString) {
        PropertyNode propertyNode = new PropertyNode();
        String trimed = encodedString.trim();
        if (trimed.length() == 0) {
            return propertyNode;
        }
        String[] elems = trimed.split("],");
        
        for (String elem : elems) {
            int index = elem.indexOf('[');
            String name = elem.substring(0, index - 1);
            Pattern pattern = Pattern.compile("(?<!\\\\),");
            String[] values = pattern.split(elem.substring(index + 1), -1);
            if (name.equals("propName")) {
                propertyNode.propName = values[0];
            } else if (name.equals("propGroupSet")) {
                for (String value : values) {
                    propertyNode.propGroupSet.add(value);
                }
            } else if (name.equals("paramMap")) {
                ContentValues paramMap = propertyNode.paramMap;
                Set<String> paramMap_TYPE = propertyNode.paramMap_TYPE;
                for (String value : values) {
                    String[] tmp = value.split("=", 2);
                    String mapKey = tmp[0];
                    // \, -> ,
                    // \\ -> \
                    // In String#replaceAll(), "\\\\" means a single backslash.
                    String mapValue =
                        tmp[1].replaceAll("\\\\,", ",").replaceAll("\\\\\\\\", "\\\\");
                    if (mapKey.equalsIgnoreCase("TYPE")) {
                        paramMap_TYPE.add(mapValue);
                    } else {
                        paramMap.put(mapKey, mapValue);
                    }
                }
            } else if (name.equals("propValue")) {
                StringBuilder builder = new StringBuilder();
                List<String> list = propertyNode.propValue_vector;
                int length = values.length;
                for (int i = 0; i < length; i++) {
                    String normValue = values[i]
                                              .replaceAll("\\\\,", ",")
                                              .replaceAll("\\\\\\\\", "\\\\");
                    list.add(normValue);
                    builder.append(normValue);
                    if (i < length - 1) {
                        builder.append(";");
                    }
                }
                propertyNode.propValue = builder.toString();
            }
        }
        
        // At this time, QUOTED-PRINTABLE is already decoded to Java String.
        // We just need to decode BASE64 String to binary.
        String encoding = propertyNode.paramMap.getAsString("ENCODING");
        if (encoding != null &&
                (encoding.equalsIgnoreCase("BASE64") ||
                        encoding.equalsIgnoreCase("B"))) {
            propertyNode.propValue_bytes =
                Base64.decodeBase64(propertyNode.propValue_vector.get(0).getBytes());
        }
        
        return propertyNode;
    }
}
Loading