summaryrefslogtreecommitdiffstats
path: root/src/com/android/providers/contacts/util/PreloadedContactsFileParser.java
blob: ad9d0c032fcfdae2957555a0eedc0d592d267494 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
package com.android.providers.contacts.util;

import android.content.ContentProviderOperation;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.util.Log;
import com.android.providers.contacts.Constants;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Responsible for parsing the preloaded_contacts.json file and helping generate database commands
 * to persist that information.
 *
 * Details about the json schema and encoding specification for the properties can be found in
 * the preloaded_contacts_schema.json file under 'res/raw'
 */
public class PreloadedContactsFileParser {

    private static final String TAG = "PreloadContacts";

    private static String TOKEN_AT = "@";
    private static String TOKEN_AT_SUB = "android.provider.ContactsContract$CommonDataKinds";
    private static String TOKEN_CONTACTS_ROOT = "contacts";
    private static String TOKEN_CONTACT_DATA = "data";
    private static String TOKEN_MIMETYPE = "@mimetype";
    private static String TOKEN_MIMETYPE_SUB = "android.provider.ContactsContract$Data.MIMETYPE";

    private static Character CLASS_NAME_DELIMITER = '.';

    private static Pattern mExpressionPattern = Pattern.compile("\\{\\{(.+?)\\}\\}");

    private boolean mDebug;
    private JSONObject mJsonRoot;
    private HashMap<String,String> mResolvedNameCache;

    public PreloadedContactsFileParser(InputStream inputStream) throws JSONException {
        mDebug = Log.isLoggable(Constants.TAG_DEBUG_PRELOAD_CONTACTS, Log.DEBUG);
        String jsonString = convertInputStreamToString(inputStream);
        mJsonRoot = new JSONObject(jsonString);
        mResolvedNameCache = new HashMap<String, String>();
    }

    private String convertInputStreamToString(InputStream is) {
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();

        String line = null;
        try {
            while ((line = br.readLine()) != null) {
                sb.append(line).append('\n');
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }

    /**
     * Parses the json object and creates the necessary {@link ContentProviderOperation}s to
     * construct the contacts specified
     */
    public ArrayList<ContentProviderOperation> parseForContacts() {
        try {
            ArrayList<ContentProviderOperation> cpOps = new ArrayList<ContentProviderOperation>();
            JSONArray contacts = mJsonRoot.getJSONArray(TOKEN_CONTACTS_ROOT);
            int numContacts = contacts.length();

            for (int i = 0; i < numContacts; ++i) {
                JSONArray contactData = contacts.getJSONObject(i).getJSONArray(TOKEN_CONTACT_DATA);
                int rawEntries = contactData.length();

                // create a new raw contact entry
                cpOps.add(
                        ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                        .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
                        .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
                        .build() );
                int cvBackRef = cpOps.size() - 1;

                for (int j = 0; j < rawEntries; ++j) {
                    JSONObject rawEntry = contactData.getJSONObject(j);
                    Iterator<String> keys = rawEntry.keys();

                    // build a ContentProviderOperation to add the contact's raw entry
                    ContentProviderOperation.Builder cpoBuilder =
                            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
                    cpoBuilder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID,
                            cvBackRef);

                    while (keys.hasNext()) {
                        String key = keys.next();
                        String value = rawEntry.getString(key);

                        if (mDebug) {
                            Log.d(TAG, "parsing property : " + key);
                            Log.d(TAG, "parsing property value : " + value);
                        }

                        String resolvedKey = null;
                        // keys always need interpolation
                        resolvedKey = resolvePropertyName(key);
                        // determine if the property is an expression that need to be evaluated
                        String resolvedValue = value;
                        Matcher matcher = mExpressionPattern.matcher(value);
                        if (matcher.matches()) {
                            matcher.reset();
                            matcher.find();
                            resolvedValue = resolvePropertyName(matcher.group(1));
                        }

                        if (mDebug) {
                            Log.d(TAG, "resolved property name : " + resolvedKey);
                            Log.d(TAG, "resolved property value : " + resolvedValue);
                        }

                        if (TextUtils.isEmpty(resolvedKey) || TextUtils.isEmpty(resolvedValue)) {
                            // don't persist this raw_contact value
                            continue;
                        } else {
                            cpoBuilder.withValue(resolvedKey, resolvedValue);
                        }

                    }

                    cpOps.add(cpoBuilder.build());
                }

            }
            return cpOps;

        } catch (JSONException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * parses an object's property name to determine its codified value
     */
    private String resolvePropertyName(String encodedName) {
        if (TextUtils.isEmpty(encodedName)) {
            return null;
        }

        if (mResolvedNameCache.containsKey(encodedName)) {
            return mResolvedNameCache.get(encodedName);
        }

        String unwrappedName = encodedName;
        // check if any substitution rules apply
        if (TextUtils.equals(TOKEN_MIMETYPE, encodedName)) {
            unwrappedName = TOKEN_MIMETYPE_SUB;
        } else if (encodedName.startsWith(TOKEN_AT)) {
            unwrappedName = encodedName.replace(TOKEN_AT, TOKEN_AT_SUB);
        }

        if (mDebug) {
            Log.d(TAG, "encoded property name : " + encodedName);
            Log.d(TAG, "resolved property name : " + unwrappedName);
        }

        String resolvedName = resolveCodifiedName(unwrappedName);
        mResolvedNameCache.put(encodedName, resolvedName);
        return resolvedName;
    }

    /**
     * returns the string-ified value of the Java field the property name points to
     */
    private String resolveCodifiedName(String absoluteName) {
        int delimiterIndex = TextUtils.lastIndexOf(absoluteName, CLASS_NAME_DELIMITER);
        // ensure there is a field identifier to read
        if (delimiterIndex == -1 || delimiterIndex >= absoluteName.length() - 1) {
            return null;
        }

        String className = TextUtils.substring(absoluteName, 0, delimiterIndex);
        String fieldName = absoluteName.substring(delimiterIndex + 1);

        if (mDebug) {
            Log.d(TAG, "property's class : " + className);
            Log.d(TAG, "property's field : " + fieldName);
        }

        try {
            Class clazz = Class.forName(className);
            Field field = clazz.getField(fieldName);
            String fieldValue = field.get(clazz).toString();
            if (mDebug) {
                Log.d(TAG, "fully resolved property name : " + fieldValue);
            }
            return fieldValue;

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return null;
    }

}