/* * 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_GOOGLE_ACCOUNT = "GOOGLE_ACCOUNT"; private final static String GOOGLE_ACCOUNT_TYPE = "com.google"; private final static String ENUM_VALUE_FOR_CUSTOM_ACCOUNT = "CUSTOM_ACCOUNT"; private final static String ACCOUNT_NAME = "account_name"; private final static String DATA_SET = "data_set"; private final static String ENUM_FOR_PLUS_DATA_SET = "GOOGLE_PLUS"; private final static String ENUM_FOR_CUSTOM_DATA_SET = "CUSTOM"; private final static String PLUS_DATA_SET_TYPE = "plus"; private final static String CUSTOM_DATA_SET = "custom_data_set"; 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 String mDataHashId; final boolean mIsPrimary; final boolean mIsSuperPrimary; final ArrayList mUsageStatsList; @NeededForTesting public FieldData(String dataHashId, boolean isPrimary, boolean isSuperPrimary, ArrayList usageStatsList) { this.mDataHashId = dataHashId; this.mIsPrimary = isPrimary; this.mIsSuperPrimary = isSuperPrimary; this.mUsageStatsList = usageStatsList; } } @NeededForTesting public static class RawContactInfo { final String mBackupId; final String mAccountType; final String mAccountName; final String mDataSet; @NeededForTesting public RawContactInfo(String backupId, String accountType, String accountName, String dataSet) { this.mBackupId = backupId; this.mAccountType = accountType; this.mAccountName = accountName; mDataSet = dataSet; } } @NeededForTesting public static class AggregationData { final RawContactInfo mRawContactInfo1; final RawContactInfo mRawContactInfo2; final String mType; @NeededForTesting public AggregationData(RawContactInfo rawContactInfo1, RawContactInfo rawContactInfo2, String type) { this.mRawContactInfo1 = rawContactInfo1; this.mRawContactInfo2 = rawContactInfo2; this.mType = type; } } @NeededForTesting public static class MetadataEntry { final RawContactInfo mRawContactInfo; final int mSendToVoicemail; final int mStarred; final int mPinned; final ArrayList mFieldDatas; final ArrayList mAggregationDatas; @NeededForTesting public MetadataEntry(RawContactInfo rawContactInfo, int sendToVoicemail, int starred, int pinned, ArrayList fieldDatas, ArrayList aggregationDatas) { this.mRawContactInfo = rawContactInfo; 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 uniqueContactJSON = root.getJSONObject(UNIQUE_CONTACT_ID); final RawContactInfo rawContactInfo = parseUniqueContact(uniqueContactJSON); // Parse contactPrefs to get sendToVoicemail, starred, pinned. final JSONObject contactPrefs = root.getJSONObject(CONTACT_PREFS); final boolean sendToVoicemail = contactPrefs.has(SEND_TO_VOICEMAIL) ? contactPrefs.getBoolean(SEND_TO_VOICEMAIL) : false; final boolean starred = contactPrefs.has(STARRED) ? contactPrefs.getBoolean(STARRED) : false; final int pinned = contactPrefs.has(PINNED) ? contactPrefs.getInt(PINNED) : 0; // Parse aggregationDatas final ArrayList aggregationsList = new ArrayList(); 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 RawContactInfo aggregationContact1 = parseUniqueContact(rawContact1); final JSONObject rawContact2 = contacts.getJSONObject(1); final RawContactInfo aggregationContact2 = parseUniqueContact(rawContact2); final String type = aggregationData.getString(TYPE); if (TextUtils.isEmpty(type)) { throw new IllegalArgumentException("Aggregation type cannot be empty."); } final AggregationData aggregation = new AggregationData( aggregationContact1, aggregationContact2, type); aggregationsList.add(aggregation); } } // Parse fieldDatas final ArrayList fieldDatasList = new ArrayList(); 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 String dataHashId = fieldData.getString(FIELD_DATA_ID); if (TextUtils.isEmpty(dataHashId)) { throw new IllegalArgumentException("Field data hash id cannot be empty."); } 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 usageStatsList = new ArrayList(); 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(dataHashId, isPrimary, isSuperPrimary, usageStatsList); fieldDatasList.add(fieldDataParse); } } final MetadataEntry metaDataEntry = new MetadataEntry(rawContactInfo, sendToVoicemail ? 1 : 0, starred ? 1 : 0, pinned, fieldDatasList, aggregationsList); return metaDataEntry; } catch (JSONException e) { throw new IllegalArgumentException("JSON Exception.", e); } } private static RawContactInfo parseUniqueContact(JSONObject uniqueContactJSON) { try { final String backupId = uniqueContactJSON.getString(CONTACT_ID); final String accountName = uniqueContactJSON.getString(ACCOUNT_NAME); String accountType = uniqueContactJSON.getString(ACCOUNT_TYPE); if (ENUM_VALUE_FOR_GOOGLE_ACCOUNT.equals(accountType)) { accountType = GOOGLE_ACCOUNT_TYPE; } else if (ENUM_VALUE_FOR_CUSTOM_ACCOUNT.equals(accountType)) { accountType = uniqueContactJSON.getString(CUSTOM_ACCOUNT_TYPE); } else { throw new IllegalArgumentException("Unknown account type."); } String dataSet = null; switch (uniqueContactJSON.getString(DATA_SET)) { case ENUM_FOR_PLUS_DATA_SET: dataSet = PLUS_DATA_SET_TYPE; break; case ENUM_FOR_CUSTOM_DATA_SET: dataSet = uniqueContactJSON.getString(CUSTOM_DATA_SET); break; } if (TextUtils.isEmpty(backupId) || TextUtils.isEmpty(accountType) || TextUtils.isEmpty(accountName)) { throw new IllegalArgumentException( "Contact backup id, account type, account name cannot be empty."); } final RawContactInfo rawContactInfo = new RawContactInfo( backupId, accountType, accountName, dataSet); return rawContactInfo; } catch (JSONException e) { throw new IllegalArgumentException("JSON Exception.", e); } } }