• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
UsageStats(String usageType, long lastTimeUsed, int timesUsed)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
FieldData(String dataHashId, boolean isPrimary, boolean isSuperPrimary, ArrayList<UsageStats> usageStatsList)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
RawContactInfo(String backupId, String accountType, String accountName, String dataSet)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
AggregationData(RawContactInfo rawContactInfo1, RawContactInfo rawContactInfo2, String type)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
MetadataEntry(RawContactInfo rawContactInfo, int sendToVoicemail, int starred, int pinned, ArrayList<FieldData> fieldDatas, ArrayList<AggregationData> aggregationDatas)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
parseDataToMetaDataEntry(String inputData)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 
parseUniqueContact(JSONObject uniqueContactJSON)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