1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Populates data fields from Android contacts profile API (i.e. "me" contact).
6
7package org.chromium.components.browser.autofill;
8
9import android.content.ContentResolver;
10import android.content.Context;
11import android.content.pm.PackageManager;
12import android.database.Cursor;
13import android.net.Uri;
14import android.provider.ContactsContract;
15
16import org.chromium.base.CalledByNative;
17import org.chromium.base.JNINamespace;
18
19/**
20 * Loads user profile information stored under the "Me" contact.
21 * Requires permissions: READ_CONTACTS and READ_PROFILE.
22 */
23@JNINamespace("autofill")
24public class PersonalAutofillPopulator {
25    /**
26     * SQL query definitions for obtaining specific profile information.
27     */
28    private abstract static class ProfileQuery {
29        Uri profileDataUri = Uri.withAppendedPath(
30                ContactsContract.Profile.CONTENT_URI,
31                ContactsContract.Contacts.Data.CONTENT_DIRECTORY
32                );
33        public abstract String[] projection();
34        public abstract String mimeType();
35    }
36
37    private static class EmailProfileQuery extends ProfileQuery {
38        private static final int EMAIL_ADDRESS = 0;
39
40        @Override
41        public String[] projection() {
42            return new String[] {
43                ContactsContract.CommonDataKinds.Email.ADDRESS,
44            };
45        }
46
47        @Override
48        public String mimeType() {
49            return ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE;
50        }
51    }
52
53    private static class PhoneProfileQuery extends ProfileQuery {
54        private static final int NUMBER = 0;
55
56        @Override
57        public String[] projection() {
58            return new String[] {
59                ContactsContract.CommonDataKinds.Phone.NUMBER,
60            };
61        }
62
63        @Override
64        public String mimeType() {
65            return ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE;
66        }
67    }
68
69    private static class AddressProfileQuery extends ProfileQuery {
70        private static final int STREET = 0;
71        private static final int POBOX = 1;
72        private static final int NEIGHBORHOOD = 2;
73        private static final int CITY = 3;
74        private static final int REGION = 4;
75        private static final int POSTALCODE = 5;
76        private static final int COUNTRY = 6;
77
78        @Override
79        public String[] projection() {
80            return new String[] {
81                ContactsContract.CommonDataKinds.StructuredPostal.STREET,
82                    ContactsContract.CommonDataKinds.StructuredPostal.POBOX,
83                    ContactsContract.CommonDataKinds.StructuredPostal.NEIGHBORHOOD,
84                    ContactsContract.CommonDataKinds.StructuredPostal.CITY,
85                    ContactsContract.CommonDataKinds.StructuredPostal.REGION,
86                    ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE,
87                    ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY,
88            };
89        }
90
91        @Override
92        public String mimeType() {
93            return ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE;
94        }
95    }
96
97    private static class NameProfileQuery extends ProfileQuery {
98        private static final int GIVEN_NAME = 0;
99        private static final int MIDDLE_NAME = 1;
100        private static final int FAMILY_NAME = 2;
101        private static final int SUFFIX = 3;
102
103        @Override
104        public String[] projection() {
105            return new String[] {
106                ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME,
107                    ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME,
108                    ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME,
109                    ContactsContract.CommonDataKinds.StructuredName.SUFFIX
110            };
111        }
112
113        @Override
114        public String mimeType() {
115            return ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE;
116        }
117    }
118
119    /**
120     * Takes a query object, transforms into actual query and returns cursor.
121     * Primary contact values will be first.
122     */
123    private Cursor cursorFromProfileQuery(ProfileQuery query, ContentResolver contentResolver) {
124        String sortDescriptor = ContactsContract.Contacts.Data.IS_PRIMARY + " DESC";
125        return contentResolver.query(
126                query.profileDataUri,
127                query.projection(),
128                ContactsContract.Contacts.Data.MIMETYPE + " = ?",
129                new String[]{query.mimeType()},
130                sortDescriptor
131                );
132    }
133    // Extracted data variables.
134    private String[] mEmailAddresses;
135    private String mGivenName;
136    private String mMiddleName;
137    private String mFamilyName;
138    private String mSuffix;
139    private String mPobox;
140    private String mStreet;
141    private String mNeighborhood;
142    private String mCity;
143    private String mRegion;
144    private String mCountry;
145    private String mPostalCode;
146    private String[] mPhoneNumbers;
147    private boolean mHasPermissions;
148
149    /**
150     * Constructor
151     * @param context a valid android context reference
152     */
153    PersonalAutofillPopulator(Context context) {
154        mHasPermissions = hasPermissions(context);
155        if (mHasPermissions) {
156            ContentResolver contentResolver = context.getContentResolver();
157            populateName(contentResolver);
158            populateEmail(contentResolver);
159            populateAddress(contentResolver);
160            populatePhone(contentResolver);
161        }
162    }
163
164    // Check if the user has granted permissions.
165    private boolean hasPermissions(Context context) {
166        String [] permissions = {
167            "android.permission.READ_CONTACTS",
168            "android.permission.READ_PROFILE"
169        };
170        for (String permission : permissions) {
171            int res = context.checkCallingOrSelfPermission(permission);
172            if (res != PackageManager.PERMISSION_GRANTED) return false;
173        }
174        return true;
175    }
176
177    // Populating data fields.
178    private void populateName(ContentResolver contentResolver) {
179        NameProfileQuery nameProfileQuery = new NameProfileQuery();
180        Cursor nameCursor = cursorFromProfileQuery(nameProfileQuery, contentResolver);
181        if (nameCursor.moveToNext()) {
182            mGivenName = nameCursor.getString(nameProfileQuery.GIVEN_NAME);
183            mMiddleName = nameCursor.getString(nameProfileQuery.MIDDLE_NAME);
184            mFamilyName = nameCursor.getString(nameProfileQuery.FAMILY_NAME);
185            mSuffix = nameCursor.getString(nameProfileQuery.SUFFIX);
186        }
187        nameCursor.close();
188    }
189
190    private void populateEmail(ContentResolver contentResolver) {
191        EmailProfileQuery emailProfileQuery = new EmailProfileQuery();
192        Cursor emailCursor = cursorFromProfileQuery(emailProfileQuery, contentResolver);
193        mEmailAddresses = new String[emailCursor.getCount()];
194        for (int i = 0; emailCursor.moveToNext(); i++) {
195            mEmailAddresses[i] = emailCursor.getString(emailProfileQuery.EMAIL_ADDRESS);
196        }
197        emailCursor.close();
198    }
199
200    private void populateAddress(ContentResolver contentResolver) {
201        AddressProfileQuery addressProfileQuery = new AddressProfileQuery();
202        Cursor addressCursor = cursorFromProfileQuery(addressProfileQuery, contentResolver);
203        if(addressCursor.moveToNext()) {
204            mPobox = addressCursor.getString(addressProfileQuery.POBOX);
205            mStreet = addressCursor.getString(addressProfileQuery.STREET);
206            mNeighborhood = addressCursor.getString(addressProfileQuery.NEIGHBORHOOD);
207            mCity = addressCursor.getString(addressProfileQuery.CITY);
208            mRegion = addressCursor.getString(addressProfileQuery.REGION);
209            mPostalCode = addressCursor.getString(addressProfileQuery.POSTALCODE);
210            mCountry = addressCursor.getString(addressProfileQuery.COUNTRY);
211        }
212        addressCursor.close();
213    }
214
215    private void populatePhone(ContentResolver contentResolver) {
216        PhoneProfileQuery phoneProfileQuery = new PhoneProfileQuery();
217        Cursor phoneCursor = cursorFromProfileQuery(phoneProfileQuery, contentResolver);
218        mPhoneNumbers = new String[phoneCursor.getCount()];
219        for (int i = 0; phoneCursor.moveToNext(); i++) {
220            mPhoneNumbers[i] = phoneCursor.getString(phoneProfileQuery.NUMBER);
221        }
222        phoneCursor.close();
223    }
224
225    /**
226     * Static factory method for instance creation.
227     * @param context valid Android context.
228     * @return PersonalAutofillPopulator new instance of PersonalAutofillPopulator.
229     */
230    @CalledByNative
231    static PersonalAutofillPopulator create(Context context) {
232        return new PersonalAutofillPopulator(context);
233    }
234
235    @CalledByNative
236    private String getFirstName() {
237        return mGivenName;
238    }
239
240    @CalledByNative
241    private String getLastName() {
242        return mFamilyName;
243    }
244
245    @CalledByNative
246    private String getMiddleName() {
247        return mMiddleName;
248    }
249
250    @CalledByNative
251    private String getSuffix() {
252        return mSuffix;
253    }
254
255    @CalledByNative
256    private String[] getEmailAddresses() {
257        return mEmailAddresses;
258    }
259
260    @CalledByNative
261    private String getStreet() {
262        return mStreet;
263    }
264
265    @CalledByNative
266    private String getPobox() {
267        return mPobox;
268    }
269
270    @CalledByNative
271    private String getNeighborhood() {
272        return mNeighborhood;
273    }
274
275    @CalledByNative
276    private String getCity() {
277        return mCity;
278    }
279
280    @CalledByNative
281    private String getRegion() {
282        return mRegion;
283    }
284
285    @CalledByNative
286    private String getPostalCode() {
287        return mPostalCode;
288    }
289
290    @CalledByNative
291    private String getCountry() {
292        return mCountry;
293    }
294
295    @CalledByNative
296    private String[] getPhoneNumbers() {
297        return mPhoneNumbers;
298    }
299
300    @CalledByNative
301    private boolean getHasPermissions() {
302        return mHasPermissions;
303    }
304}
305