diff options
3 files changed, 570 insertions, 0 deletions
diff --git a/src/com/android/providers/contacts/MetadataEntryParser.java b/src/com/android/providers/contacts/MetadataEntryParser.java new file mode 100644 index 0000000..a276cae --- /dev/null +++ b/src/com/android/providers/contacts/MetadataEntryParser.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2015 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.providers.contacts; + +import android.text.TextUtils; +import com.android.providers.contacts.util.NeededForTesting; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; + +@NeededForTesting +public class MetadataEntryParser { + + private final static String UNIQUE_CONTACT_ID = "unique_contact_id"; + private final static String ACCOUNT_TYPE = "account_type"; + private final static String CUSTOM_ACCOUNT_TYPE = "custom_account_type"; + private final static String ENUM_VALUE_FOR_CUSTOM_ACCOUNT = "CUSTOM_ACCOUNT"; + private final static String ACCOUNT_NAME = "account_name"; + private final static String CONTACT_ID = "contact_id"; + private final static String CONTACT_PREFS = "contact_prefs"; + private final static String SEND_TO_VOICEMAIL = "send_to_voicemail"; + private final static String STARRED = "starred"; + private final static String PINNED = "pinned"; + private final static String AGGREGATION_DATA = "aggregation_data"; + private final static String CONTACT_IDS = "contact_ids"; + private final static String TYPE = "type"; + private final static String FIELD_DATA = "field_data"; + private final static String FIELD_DATA_ID = "field_data_id"; + private final static String FIELD_DATA_PREFS = "field_data_prefs"; + private final static String IS_PRIMARY = "is_primary"; + private final static String IS_SUPER_PRIMARY = "is_super_primary"; + private final static String USAGE_STATS = "usage_stats"; + private final static String USAGE_TYPE = "usage_type"; + private final static String LAST_TIME_USED = "last_time_used"; + private final static String USAGE_COUNT = "usage_count"; + + @NeededForTesting + public static class UsageStats { + final String mUsageType; + final long mLastTimeUsed; + final int mTimesUsed; + + @NeededForTesting + public UsageStats(String usageType, long lastTimeUsed, int timesUsed) { + this.mUsageType = usageType; + this.mLastTimeUsed = lastTimeUsed; + this.mTimesUsed = timesUsed; + } + } + + @NeededForTesting + public static class FieldData { + final long mFieldDataId; + final boolean mIsPrimary; + final boolean mIsSuperPrimary; + final ArrayList<UsageStats> mUsageStatsList; + + @NeededForTesting + public FieldData(long fieldDataId, boolean isPrimary, boolean isSuperPrimary, + ArrayList<UsageStats> usageStatsList) { + this.mFieldDataId = fieldDataId; + this.mIsPrimary = isPrimary; + this.mIsSuperPrimary = isSuperPrimary; + this.mUsageStatsList = usageStatsList; + } + } + + @NeededForTesting + public static class AggregationData { + final long mRawContactId1; + final long mRawContactId2; + final String mType; + + @NeededForTesting + public AggregationData(long rawContactId1, long rawContactId2, String type) { + this.mRawContactId1 = rawContactId1; + this.mRawContactId2 = rawContactId2; + this.mType = type; + } + } + + @NeededForTesting + public static class MetadataEntry { + final long mRawContactId; + final String mAccountType; + final String mAccountName; + final int mSendToVoicemail; + final int mStarred; + final int mPinned; + final ArrayList<FieldData> mFieldDatas; + final ArrayList<AggregationData> mAggregationDatas; + + @NeededForTesting + public MetadataEntry(long rawContactId, String accountType, String accountName, + int sendToVoicemail, int starred, int pinned, + ArrayList<FieldData> fieldDatas, + ArrayList<AggregationData> aggregationDatas) { + this.mRawContactId = rawContactId; + this.mAccountType = accountType; + this.mAccountName = accountName; + this.mSendToVoicemail = sendToVoicemail; + this.mStarred = starred; + this.mPinned = pinned; + this.mFieldDatas = fieldDatas; + this.mAggregationDatas = aggregationDatas; + } + } + + @NeededForTesting + static MetadataEntry parseDataToMetaDataEntry(String inputData) { + if (TextUtils.isEmpty(inputData)) { + throw new IllegalArgumentException("Input cannot be empty."); + } + + try { + final JSONObject root = new JSONObject(inputData); + // Parse to get rawContactId and account info. + final JSONObject uniqueContact = root.getJSONObject(UNIQUE_CONTACT_ID); + final String rawContactId = uniqueContact.getString(CONTACT_ID); + String accountType = uniqueContact.getString(ACCOUNT_TYPE); + if (ENUM_VALUE_FOR_CUSTOM_ACCOUNT.equals(accountType)) { + accountType = uniqueContact.getString(CUSTOM_ACCOUNT_TYPE); + } + final String accountName = uniqueContact.getString(ACCOUNT_NAME); + if (TextUtils.isEmpty(rawContactId) || TextUtils.isEmpty(accountType) + || TextUtils.isEmpty(accountName)) { + throw new IllegalArgumentException( + "contact_id, account_type, account_name cannot be empty."); + } + + // Parse contactPrefs to get sendToVoicemail, starred, pinned. + final JSONObject contactPrefs = root.getJSONObject(CONTACT_PREFS); + final boolean sendToVoicemail = contactPrefs.getBoolean(SEND_TO_VOICEMAIL); + final boolean starred = contactPrefs.getBoolean(STARRED); + final int pinned = contactPrefs.getInt(PINNED); + + // Parse aggregationDatas + final ArrayList<AggregationData> aggregationsList = new ArrayList<AggregationData>(); + if (root.has(AGGREGATION_DATA)) { + final JSONArray aggregationDatas = root.getJSONArray(AGGREGATION_DATA); + + for (int i = 0; i < aggregationDatas.length(); i++) { + final JSONObject aggregationData = aggregationDatas.getJSONObject(i); + final JSONArray contacts = aggregationData.getJSONArray(CONTACT_IDS); + + if (contacts.length() != 2) { + throw new IllegalArgumentException( + "There should be two contacts for each aggregation."); + } + final JSONObject rawContact1 = contacts.getJSONObject(0); + final long rawContactId1 = rawContact1.getLong(CONTACT_ID); + final JSONObject rawContact2 = contacts.getJSONObject(1); + final long rawContactId2 = rawContact2.getLong(CONTACT_ID); + final String type = aggregationData.getString(TYPE); + if (TextUtils.isEmpty(type)) { + throw new IllegalArgumentException("Aggregation type cannot be empty."); + } + + final AggregationData aggregation = new AggregationData( + rawContactId1, rawContactId2, type); + aggregationsList.add(aggregation); + } + } + + // Parse fieldDatas + final ArrayList<FieldData> fieldDatasList = new ArrayList<FieldData>(); + if (root.has(FIELD_DATA)) { + final JSONArray fieldDatas = root.getJSONArray(FIELD_DATA); + + for (int i = 0; i < fieldDatas.length(); i++) { + final JSONObject fieldData = fieldDatas.getJSONObject(i); + final long fieldDataId = fieldData.getLong(FIELD_DATA_ID); + final JSONObject fieldDataPrefs = fieldData.getJSONObject(FIELD_DATA_PREFS); + final boolean isPrimary = fieldDataPrefs.getBoolean(IS_PRIMARY); + final boolean isSuperPrimary = fieldDataPrefs.getBoolean(IS_SUPER_PRIMARY); + + final ArrayList<UsageStats> usageStatsList = new ArrayList<UsageStats>(); + if (fieldData.has(USAGE_STATS)) { + final JSONArray usageStats = fieldData.getJSONArray(USAGE_STATS); + for (int j = 0; j < usageStats.length(); j++) { + final JSONObject usageStat = usageStats.getJSONObject(j); + final String usageType = usageStat.getString(USAGE_TYPE); + if (TextUtils.isEmpty(usageType)) { + throw new IllegalArgumentException("Usage type cannot be empty."); + } + final long lastTimeUsed = usageStat.getLong(LAST_TIME_USED); + final int usageCount = usageStat.getInt(USAGE_COUNT); + + final UsageStats usageStatsParsed = new UsageStats( + usageType, lastTimeUsed, usageCount); + usageStatsList.add(usageStatsParsed); + } + } + + final FieldData fieldDataParse = new FieldData(fieldDataId, isPrimary, + isSuperPrimary, usageStatsList); + fieldDatasList.add(fieldDataParse); + } + } + final MetadataEntry metaDataEntry = new MetadataEntry(Long.parseLong(rawContactId), + accountType, accountName, sendToVoicemail ? 1 : 0, starred ? 1 : 0, pinned, + fieldDatasList, aggregationsList); + return metaDataEntry; + } catch (JSONException e) { + throw new IllegalArgumentException("JSON Exception.", e); + } + } +} diff --git a/tests/assets/test1/testFileDeviceContactMetadataJSON.txt b/tests/assets/test1/testFileDeviceContactMetadataJSON.txt new file mode 100644 index 0000000..c9494d8 --- /dev/null +++ b/tests/assets/test1/testFileDeviceContactMetadataJSON.txt @@ -0,0 +1,61 @@ +{ + "unique_contact_id": { + "account_type": "CUSTOM_ACCOUNT", + "custom_account_type": "facebook", + "account_name": "android-test", + "contact_id": "1111111" + }, + "contact_prefs": { + "send_to_voicemail": true, + "starred": false, + "pinned": 2 + }, + "aggregation_data": [ + { + "type": "TOGETHER", + "contact_ids": [ + { + "contact_id": "2222222" + }, + { + "contact_id": "3333333" + } + ] + } + ], + "field_data": [ + { + "field_data_id": "1001", + "field_data_prefs": { + "is_primary": true, + "is_super_primary": true + }, + "usage_stats": [ + { + "usage_type": "CALL", + "last_time_used": 10000001, + "usage_count": 10 + }, + { + "usage_type": "SHORT_TEXT", + "last_time_used": 20000002, + "usage_count": 20 + } + ] + }, + { + "field_data_id": "1002", + "field_data_prefs": { + "is_primary": false, + "is_super_primary": false + }, + "usage_stats": [ + { + "usage_type": "LONG_TEXT", + "last_time_used": 30000003, + "usage_count": 30 + } + ] + } + ] +}
\ No newline at end of file diff --git a/tests/src/com/android/providers/contacts/MetadataEntryParserTest.java b/tests/src/com/android/providers/contacts/MetadataEntryParserTest.java new file mode 100644 index 0000000..94ca074 --- /dev/null +++ b/tests/src/com/android/providers/contacts/MetadataEntryParserTest.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2015 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.providers.contacts; + +import android.content.Context; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import com.android.providers.contacts.MetadataEntryParser.AggregationData; +import com.android.providers.contacts.MetadataEntryParser.FieldData; +import com.android.providers.contacts.MetadataEntryParser.MetadataEntry; +import com.android.providers.contacts.MetadataEntryParser.UsageStats; +import org.json.JSONException; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; + +/** + * Unit tests for {@link MetadataEntryParser}. + * + * Run the test like this: + * <code> + adb shell am instrument -e class com.android.providers.contacts.MetadataEntryParserTest -w \ + com.android.providers.contacts.tests/android.test.InstrumentationTestRunner + * </code> + */ +@SmallTest +public class MetadataEntryParserTest extends AndroidTestCase { + + public void testErrorForEmptyInput() { + try { + MetadataEntryParser.parseDataToMetaDataEntry(""); + } catch (IllegalArgumentException e) { + // Expected. + } + } + + public void testParseDataToMetadataEntry() throws IOException { + long rawContactId = 1111111; + String accountType = "facebook"; + String accountName = "android-test"; + int sendToVoicemail = 1; + int starred = 0; + int pinned = 2; + long fieldDataId1 = 1001; + String usageType1_1 = "CALL"; + long lastTimeUsed1_1 = 10000001; + int timesUsed1_1 = 10; + String usageType1_2 = "SHORT_TEXT"; + long lastTimeUsed1_2 = 20000002; + int timesUsed1_2 = 20; + long fieldDataId2 = 1002; + String usageType2 = "LONG_TEXT"; + long lastTimeUsed2 = 30000003; + int timesUsed2 = 30; + long aggregationContact1 = 2222222; + long aggregationContact2 = 3333333; + String type = "TOGETHER"; + String inputFile = "test1/testFileDeviceContactMetadataJSON.txt"; + + AggregationData aggregationData = new AggregationData( + aggregationContact1, aggregationContact2, type); + ArrayList<AggregationData> aggregationDataList = new ArrayList<>(); + aggregationDataList.add(aggregationData); + + UsageStats usageStats1_1 = new UsageStats(usageType1_1, lastTimeUsed1_1, timesUsed1_1); + UsageStats usageStats1_2 = new UsageStats(usageType1_2, lastTimeUsed1_2, timesUsed1_2); + UsageStats usageStats2 = new UsageStats(usageType2, lastTimeUsed2, timesUsed2); + + ArrayList<UsageStats> usageStats1List = new ArrayList<>(); + usageStats1List.add(usageStats1_1); + usageStats1List.add(usageStats1_2); + FieldData fieldData1 = new FieldData(fieldDataId1, true, true, usageStats1List); + + ArrayList<UsageStats> usageStats2List = new ArrayList<>(); + usageStats2List.add(usageStats2); + FieldData fieldData2 = new FieldData(fieldDataId2, false, false, usageStats2List); + + ArrayList<FieldData> fieldDataList = new ArrayList<>(); + fieldDataList.add(fieldData1); + fieldDataList.add(fieldData2); + + MetadataEntry expectedResult = new MetadataEntry(rawContactId, accountType, accountName, + sendToVoicemail, starred, pinned, fieldDataList, aggregationDataList); + + String inputJson = readAssetAsString(inputFile); + MetadataEntry metadataEntry = MetadataEntryParser.parseDataToMetaDataEntry( + inputJson.toString()); + assertMetaDataEntry(expectedResult, metadataEntry); + } + + public void testErrorForMissingContactId() { + String input = "{\"unique_contact_id\": {\n" + + " \"account_type\": \"CUSTOM_ACCOUNT\",\n" + + " \"custom_account_type\": \"facebook\",\n" + + " \"account_name\": \"android-test\"\n" + + " }}"; + try { + MetadataEntryParser.parseDataToMetaDataEntry(input); + } catch (IllegalArgumentException e) { + // Expected. + } + } + + public void testErrorForNullContactId() throws JSONException { + String input = "{\"unique_contact_id\": {\n" + + " \"account_type\": \"CUSTOM_ACCOUNT\",\n" + + " \"custom_account_type\": \"facebook\",\n" + + " \"account_name\": \"android-test\",\n" + + " \"contact_id\": \"\"\n" + + " }}"; + try { + MetadataEntryParser.parseDataToMetaDataEntry(input); + } catch (IllegalArgumentException e) { + // Expected. + } + } + + public void testErrorForNullAccountType() throws JSONException { + String input = "{\"unique_contact_id\": {\n" + + " \"account_type\": \"\",\n" + + " \"custom_account_type\": \"facebook\",\n" + + " \"account_name\": \"android-test\",\n" + + " \"contact_id\": \"\"\n" + + " }}"; + try { + MetadataEntryParser.parseDataToMetaDataEntry(input); + } catch (IllegalArgumentException e) { + // Expected. + } + } + + public void testErrorForNullAccountName() throws JSONException { + String input = "{\"unique_contact_id\": {\n" + + " \"account_type\": \"CUSTOM_ACCOUNT\",\n" + + " \"custom_account_type\": \"facebook\",\n" + + " \"account_name\": \"\",\n" + + " \"contact_id\": \"1111111\"\n" + + " }}"; + try { + MetadataEntryParser.parseDataToMetaDataEntry(input); + } catch (IllegalArgumentException e) { + // Expected. + } + } + + public void testErrorForNullFieldDataId() throws JSONException { + String input = "{\"unique_contact_id\": {\n" + + " \"account_type\": \"CUSTOM_ACCOUNT\",\n" + + " \"custom_account_type\": \"facebook\",\n" + + " \"account_name\": \"android-test\",\n" + + " \"contact_id\": \"1111111\"\n" + + " },\n" + + " \"contact_prefs\": {\n" + + " \"send_to_voicemail\": true,\n" + + " \"starred\": false,\n" + + " \"pinned\": 2\n" + + " }," + + " \"field_data\": [{\n" + + " \"field_data_id\": \"\"}]" + + "}"; + try { + MetadataEntryParser.parseDataToMetaDataEntry(input); + } catch (IllegalArgumentException e) { + // Expected. + } + } + + public void testErrorForNullAggregationType() throws JSONException { + String input = "{\n" + + " \"unique_contact_id\": {\n" + + " \"account_type\": \"CUSTOM_ACCOUNT\",\n" + + " \"custom_account_type\": \"facebook\",\n" + + " \"account_name\": \"android-test\",\n" + + " \"contact_id\": \"1111111\"\n" + + " },\n" + + " \"contact_prefs\": {\n" + + " \"send_to_voicemail\": true,\n" + + " \"starred\": false,\n" + + " \"pinned\": 2\n" + + " },\n" + + " \"aggregation_data\": [\n" + + " {\n" + + " \"type\": \"\",\n" + + " \"contact_ids\": [\n" + + " {\n" + + " \"contact_id\": \"2222222\"\n" + + " },\n" + + " {\n" + + " \"contact_id\": \"3333333\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]}"; + try { + MetadataEntryParser.parseDataToMetaDataEntry(input); + } catch (IllegalArgumentException e) { + // Expected. + } + } + + private String readAssetAsString(String fileName) throws IOException { + Context context = getTestContext(); + InputStream input = context.getAssets().open(fileName); + ByteArrayOutputStream contents = new ByteArrayOutputStream(); + int len; + byte[] data = new byte[1024]; + do { + len = input.read(data); + if (len > 0) contents.write(data, 0, len); + } while (len == data.length); + return contents.toString(); + } + + private void assertMetaDataEntry(MetadataEntry entry1, MetadataEntry entry2) { + assertEquals(entry1.mRawContactId, entry2.mRawContactId); + assertEquals(entry1.mAccountType, entry2.mAccountType); + assertEquals(entry1.mAccountName, entry2.mAccountName); + assertEquals(entry1.mSendToVoicemail, entry2.mSendToVoicemail); + assertEquals(entry1.mStarred, entry2.mStarred); + assertEquals(entry1.mPinned, entry2.mPinned); + assertAggregationDataListEquals(entry1.mAggregationDatas, entry2.mAggregationDatas); + assertFieldDataListEquals(entry1.mFieldDatas, entry2.mFieldDatas); + } + + private void assertAggregationDataListEquals(ArrayList<AggregationData> aggregationList1, + ArrayList<AggregationData> aggregationList2) { + assertEquals(aggregationList1.size(), aggregationList2.size()); + for (int i = 0; i < aggregationList1.size(); i++) { + assertAggregationDataEquals(aggregationList1.get(i), aggregationList2.get(i)); + } + } + + private void assertAggregationDataEquals(AggregationData aggregationData1, + AggregationData aggregationData2) { + assertEquals(aggregationData1.mRawContactId1, aggregationData2.mRawContactId1); + assertEquals(aggregationData1.mRawContactId2, aggregationData2.mRawContactId2); + assertEquals(aggregationData1.mType, aggregationData2.mType); + } + + private void assertFieldDataListEquals(ArrayList<FieldData> fieldDataList1, + ArrayList<FieldData> fieldDataList2) { + assertEquals(fieldDataList1.size(), fieldDataList2.size()); + for (int i = 0; i < fieldDataList1.size(); i++) { + assertFieldDataEquals(fieldDataList1.get(i), fieldDataList2.get(i)); + } + } + + private void assertFieldDataEquals(FieldData fieldData1, FieldData fieldData2) { + assertEquals(fieldData1.mFieldDataId, fieldData2.mFieldDataId); + assertEquals(fieldData1.mIsPrimary, fieldData2.mIsPrimary); + assertEquals(fieldData1.mIsSuperPrimary, fieldData2.mIsSuperPrimary); + assertUsageStatsListEquals(fieldData1.mUsageStatsList, fieldData2.mUsageStatsList); + } + + private void assertUsageStatsListEquals(ArrayList<UsageStats> usageStatsList1, + ArrayList<UsageStats> usageStatsList2) { + assertEquals(usageStatsList1.size(), usageStatsList2.size()); + for (int i = 0; i < usageStatsList1.size(); i++) { + assertUsageStatsEquals(usageStatsList1.get(i), usageStatsList2.get(i)); + } + } + + private void assertUsageStatsEquals(UsageStats usageStats1, UsageStats usageStats2) { + assertEquals(usageStats1.mUsageType, usageStats2.mUsageType); + assertEquals(usageStats1.mLastTimeUsed, usageStats2.mLastTimeUsed); + assertEquals(usageStats1.mTimesUsed, usageStats2.mTimesUsed); + } +} |