Home | History | Annotate | Download | only in contacts
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.providers.contacts;
     18 
     19 import android.text.TextUtils;
     20 import com.android.providers.contacts.util.NeededForTesting;
     21 
     22 import org.json.JSONArray;
     23 import org.json.JSONException;
     24 import org.json.JSONObject;
     25 
     26 import java.util.ArrayList;
     27 
     28 @NeededForTesting
     29 public class MetadataEntryParser {
     30 
     31     private final static String UNIQUE_CONTACT_ID = "unique_contact_id";
     32     private final static String ACCOUNT_TYPE = "account_type";
     33     private final static String CUSTOM_ACCOUNT_TYPE = "custom_account_type";
     34     private final static String ENUM_VALUE_FOR_GOOGLE_ACCOUNT = "GOOGLE_ACCOUNT";
     35     private final static String GOOGLE_ACCOUNT_TYPE = "com.google";
     36     private final static String ENUM_VALUE_FOR_CUSTOM_ACCOUNT = "CUSTOM_ACCOUNT";
     37     private final static String ACCOUNT_NAME = "account_name";
     38     private final static String DATA_SET = "data_set";
     39     private final static String ENUM_FOR_PLUS_DATA_SET = "GOOGLE_PLUS";
     40     private final static String ENUM_FOR_CUSTOM_DATA_SET = "CUSTOM";
     41     private final static String PLUS_DATA_SET_TYPE = "plus";
     42     private final static String CUSTOM_DATA_SET = "custom_data_set";
     43     private final static String CONTACT_ID = "contact_id";
     44     private final static String CONTACT_PREFS = "contact_prefs";
     45     private final static String SEND_TO_VOICEMAIL = "send_to_voicemail";
     46     private final static String STARRED = "starred";
     47     private final static String PINNED = "pinned";
     48     private final static String AGGREGATION_DATA = "aggregation_data";
     49     private final static String CONTACT_IDS = "contact_ids";
     50     private final static String TYPE = "type";
     51     private final static String FIELD_DATA = "field_data";
     52     private final static String FIELD_DATA_ID = "field_data_id";
     53     private final static String FIELD_DATA_PREFS = "field_data_prefs";
     54     private final static String IS_PRIMARY = "is_primary";
     55     private final static String IS_SUPER_PRIMARY = "is_super_primary";
     56     private final static String USAGE_STATS = "usage_stats";
     57     private final static String USAGE_TYPE = "usage_type";
     58     private final static String LAST_TIME_USED = "last_time_used";
     59     private final static String USAGE_COUNT = "usage_count";
     60 
     61     @NeededForTesting
     62     public static class UsageStats {
     63         final String mUsageType;
     64         final long mLastTimeUsed;
     65         final int mTimesUsed;
     66 
     67         @NeededForTesting
     68         public UsageStats(String usageType, long lastTimeUsed, int timesUsed) {
     69             this.mUsageType = usageType;
     70             this.mLastTimeUsed = lastTimeUsed;
     71             this.mTimesUsed = timesUsed;
     72         }
     73     }
     74 
     75     @NeededForTesting
     76     public static class FieldData {
     77         final String mDataHashId;
     78         final boolean mIsPrimary;
     79         final boolean mIsSuperPrimary;
     80         final ArrayList<UsageStats> mUsageStatsList;
     81 
     82         @NeededForTesting
     83         public FieldData(String dataHashId, boolean isPrimary, boolean isSuperPrimary,
     84                 ArrayList<UsageStats> usageStatsList) {
     85             this.mDataHashId = dataHashId;
     86             this.mIsPrimary = isPrimary;
     87             this.mIsSuperPrimary = isSuperPrimary;
     88             this.mUsageStatsList = usageStatsList;
     89         }
     90     }
     91 
     92     @NeededForTesting
     93     public static class RawContactInfo {
     94         final String mBackupId;
     95         final String mAccountType;
     96         final String mAccountName;
     97         final String mDataSet;
     98 
     99         @NeededForTesting
    100         public RawContactInfo(String backupId, String accountType, String accountName,
    101                 String dataSet) {
    102             this.mBackupId = backupId;
    103             this.mAccountType = accountType;
    104             this.mAccountName = accountName;
    105             mDataSet = dataSet;
    106         }
    107     }
    108 
    109     @NeededForTesting
    110     public static class AggregationData {
    111         final RawContactInfo mRawContactInfo1;
    112         final RawContactInfo mRawContactInfo2;
    113         final String mType;
    114 
    115         @NeededForTesting
    116         public AggregationData(RawContactInfo rawContactInfo1, RawContactInfo rawContactInfo2,
    117                 String type) {
    118             this.mRawContactInfo1 = rawContactInfo1;
    119             this.mRawContactInfo2 = rawContactInfo2;
    120             this.mType = type;
    121         }
    122     }
    123 
    124     @NeededForTesting
    125     public static class MetadataEntry {
    126         final RawContactInfo mRawContactInfo;
    127         final int mSendToVoicemail;
    128         final int mStarred;
    129         final int mPinned;
    130         final ArrayList<FieldData> mFieldDatas;
    131         final ArrayList<AggregationData> mAggregationDatas;
    132 
    133         @NeededForTesting
    134         public MetadataEntry(RawContactInfo rawContactInfo,
    135                 int sendToVoicemail, int starred, int pinned,
    136                 ArrayList<FieldData> fieldDatas,
    137                 ArrayList<AggregationData> aggregationDatas) {
    138             this.mRawContactInfo = rawContactInfo;
    139             this.mSendToVoicemail = sendToVoicemail;
    140             this.mStarred = starred;
    141             this.mPinned = pinned;
    142             this.mFieldDatas = fieldDatas;
    143             this.mAggregationDatas = aggregationDatas;
    144         }
    145     }
    146 
    147     @NeededForTesting
    148     static MetadataEntry parseDataToMetaDataEntry(String inputData) {
    149         if (TextUtils.isEmpty(inputData)) {
    150             throw new IllegalArgumentException("Input cannot be empty.");
    151         }
    152 
    153         try {
    154             final JSONObject root = new JSONObject(inputData);
    155             // Parse to get rawContactId and account info.
    156             final JSONObject uniqueContactJSON = root.getJSONObject(UNIQUE_CONTACT_ID);
    157             final RawContactInfo rawContactInfo = parseUniqueContact(uniqueContactJSON);
    158 
    159             // Parse contactPrefs to get sendToVoicemail, starred, pinned.
    160             final JSONObject contactPrefs = root.getJSONObject(CONTACT_PREFS);
    161             final boolean sendToVoicemail = contactPrefs.has(SEND_TO_VOICEMAIL)
    162                     ? contactPrefs.getBoolean(SEND_TO_VOICEMAIL) : false;
    163             final boolean starred = contactPrefs.has(STARRED)
    164                     ? contactPrefs.getBoolean(STARRED) : false;
    165             final int pinned = contactPrefs.has(PINNED) ? contactPrefs.getInt(PINNED) : 0;
    166 
    167             // Parse aggregationDatas
    168             final ArrayList<AggregationData> aggregationsList = new ArrayList<AggregationData>();
    169             if (root.has(AGGREGATION_DATA)) {
    170                 final JSONArray aggregationDatas = root.getJSONArray(AGGREGATION_DATA);
    171 
    172                 for (int i = 0; i < aggregationDatas.length(); i++) {
    173                     final JSONObject aggregationData = aggregationDatas.getJSONObject(i);
    174                     final JSONArray contacts = aggregationData.getJSONArray(CONTACT_IDS);
    175 
    176                     if (contacts.length() != 2) {
    177                         throw new IllegalArgumentException(
    178                                 "There should be two contacts for each aggregation.");
    179                     }
    180                     final JSONObject rawContact1 = contacts.getJSONObject(0);
    181                     final RawContactInfo aggregationContact1 = parseUniqueContact(rawContact1);
    182                     final JSONObject rawContact2 = contacts.getJSONObject(1);
    183                     final RawContactInfo aggregationContact2 = parseUniqueContact(rawContact2);
    184                     final String type = aggregationData.getString(TYPE);
    185                     if (TextUtils.isEmpty(type)) {
    186                         throw new IllegalArgumentException("Aggregation type cannot be empty.");
    187                     }
    188 
    189                     final AggregationData aggregation = new AggregationData(
    190                             aggregationContact1, aggregationContact2, type);
    191                     aggregationsList.add(aggregation);
    192                 }
    193             }
    194 
    195             // Parse fieldDatas
    196             final ArrayList<FieldData> fieldDatasList = new ArrayList<FieldData>();
    197             if (root.has(FIELD_DATA)) {
    198                 final JSONArray fieldDatas = root.getJSONArray(FIELD_DATA);
    199 
    200                 for (int i = 0; i < fieldDatas.length(); i++) {
    201                     final JSONObject fieldData = fieldDatas.getJSONObject(i);
    202                     final String dataHashId = fieldData.getString(FIELD_DATA_ID);
    203                     if (TextUtils.isEmpty(dataHashId)) {
    204                         throw new IllegalArgumentException("Field data hash id cannot be empty.");
    205                     }
    206                     final JSONObject fieldDataPrefs = fieldData.getJSONObject(FIELD_DATA_PREFS);
    207                     final boolean isPrimary = fieldDataPrefs.getBoolean(IS_PRIMARY);
    208                     final boolean isSuperPrimary = fieldDataPrefs.getBoolean(IS_SUPER_PRIMARY);
    209 
    210                     final ArrayList<UsageStats> usageStatsList = new ArrayList<UsageStats>();
    211                     if (fieldData.has(USAGE_STATS)) {
    212                         final JSONArray usageStats = fieldData.getJSONArray(USAGE_STATS);
    213                         for (int j = 0; j < usageStats.length(); j++) {
    214                             final JSONObject usageStat = usageStats.getJSONObject(j);
    215                             final String usageType = usageStat.getString(USAGE_TYPE);
    216                             if (TextUtils.isEmpty(usageType)) {
    217                                 throw new IllegalArgumentException("Usage type cannot be empty.");
    218                             }
    219                             final long lastTimeUsed = usageStat.getLong(LAST_TIME_USED);
    220                             final int usageCount = usageStat.getInt(USAGE_COUNT);
    221 
    222                             final UsageStats usageStatsParsed = new UsageStats(
    223                                     usageType, lastTimeUsed, usageCount);
    224                             usageStatsList.add(usageStatsParsed);
    225                         }
    226                     }
    227 
    228                     final FieldData fieldDataParse = new FieldData(dataHashId, isPrimary,
    229                             isSuperPrimary, usageStatsList);
    230                     fieldDatasList.add(fieldDataParse);
    231                 }
    232             }
    233             final MetadataEntry metaDataEntry = new MetadataEntry(rawContactInfo,
    234                     sendToVoicemail ? 1 : 0, starred ? 1 : 0, pinned,
    235                     fieldDatasList, aggregationsList);
    236             return metaDataEntry;
    237         } catch (JSONException e) {
    238             throw new IllegalArgumentException("JSON Exception.", e);
    239         }
    240     }
    241 
    242     private static RawContactInfo parseUniqueContact(JSONObject uniqueContactJSON) {
    243         try {
    244             final String backupId = uniqueContactJSON.getString(CONTACT_ID);
    245             final String accountName = uniqueContactJSON.getString(ACCOUNT_NAME);
    246             String accountType = uniqueContactJSON.getString(ACCOUNT_TYPE);
    247             if (ENUM_VALUE_FOR_GOOGLE_ACCOUNT.equals(accountType)) {
    248                 accountType = GOOGLE_ACCOUNT_TYPE;
    249             } else if (ENUM_VALUE_FOR_CUSTOM_ACCOUNT.equals(accountType)) {
    250                 accountType = uniqueContactJSON.getString(CUSTOM_ACCOUNT_TYPE);
    251             } else {
    252                 throw new IllegalArgumentException("Unknown account type.");
    253             }
    254 
    255             String dataSet = null;
    256             switch (uniqueContactJSON.getString(DATA_SET)) {
    257                 case ENUM_FOR_PLUS_DATA_SET:
    258                     dataSet = PLUS_DATA_SET_TYPE;
    259                     break;
    260                 case ENUM_FOR_CUSTOM_DATA_SET:
    261                     dataSet = uniqueContactJSON.getString(CUSTOM_DATA_SET);
    262                     break;
    263             }
    264             if (TextUtils.isEmpty(backupId) || TextUtils.isEmpty(accountType)
    265                     || TextUtils.isEmpty(accountName)) {
    266                 throw new IllegalArgumentException(
    267                         "Contact backup id, account type, account name cannot be empty.");
    268             }
    269             final RawContactInfo rawContactInfo = new RawContactInfo(
    270                     backupId, accountType, accountName, dataSet);
    271             return rawContactInfo;
    272         } catch (JSONException e) {
    273             throw new IllegalArgumentException("JSON Exception.", e);
    274         }
    275     }
    276 }
    277