1/*
2 * Copyright (C) 2009 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
17package com.android.loaderapp.model;
18
19import com.android.loaderapp.R;
20import com.android.loaderapp.model.EntityDelta.ValuesDelta;
21import com.google.android.collect.Lists;
22
23import android.accounts.Account;
24import android.content.ContentProviderOperation;
25import android.content.ContentProviderResult;
26import android.content.ContentResolver;
27import android.content.ContentUris;
28import android.content.ContentValues;
29import android.content.Context;
30import android.content.OperationApplicationException;
31import android.database.Cursor;
32import android.os.RemoteException;
33import android.provider.ContactsContract;
34import android.provider.ContactsContract.Groups;
35import android.provider.ContactsContract.RawContacts;
36import android.provider.ContactsContract.CommonDataKinds.Email;
37import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
38import android.provider.ContactsContract.CommonDataKinds.Phone;
39import android.provider.ContactsContract.Contacts.Data;
40
41import java.util.ArrayList;
42
43public class GoogleSource extends FallbackSource {
44    public static final String ACCOUNT_TYPE = "com.google";
45    public GoogleSource(String resPackageName) {
46        this.accountType = ACCOUNT_TYPE;
47        this.resPackageName = null;
48        this.summaryResPackageName = resPackageName;
49    }
50
51    @Override
52    protected void inflate(Context context, int inflateLevel) {
53
54        inflateStructuredName(context, inflateLevel);
55        inflateNickname(context, inflateLevel);
56        inflatePhone(context, inflateLevel);
57        inflateEmail(context, inflateLevel);
58        inflateStructuredPostal(context, inflateLevel);
59        inflateIm(context, inflateLevel);
60        inflateOrganization(context, inflateLevel);
61        inflatePhoto(context, inflateLevel);
62        inflateNote(context, inflateLevel);
63        inflateWebsite(context, inflateLevel);
64        inflateEvent(context, inflateLevel);
65
66        // TODO: GOOGLE: GROUPMEMBERSHIP
67
68        setInflatedLevel(inflateLevel);
69
70    }
71
72    @Override
73    protected DataKind inflateStructuredName(Context context, int inflateLevel) {
74        return super.inflateStructuredName(context, inflateLevel);
75    }
76
77    @Override
78    protected DataKind inflateNickname(Context context, int inflateLevel) {
79        return super.inflateNickname(context, inflateLevel);
80    }
81
82    @Override
83    protected DataKind inflatePhone(Context context, int inflateLevel) {
84        final DataKind kind = super.inflatePhone(context, ContactsSource.LEVEL_MIMETYPES);
85
86        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
87            kind.typeColumn = Phone.TYPE;
88            kind.typeList = Lists.newArrayList();
89            kind.typeList.add(buildPhoneType(Phone.TYPE_HOME));
90            kind.typeList.add(buildPhoneType(Phone.TYPE_MOBILE));
91            kind.typeList.add(buildPhoneType(Phone.TYPE_WORK));
92            kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_WORK).setSecondary(true));
93            kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_HOME).setSecondary(true));
94            kind.typeList.add(buildPhoneType(Phone.TYPE_PAGER).setSecondary(true));
95            kind.typeList.add(buildPhoneType(Phone.TYPE_OTHER));
96            kind.typeList.add(buildPhoneType(Phone.TYPE_CUSTOM).setSecondary(true).setCustomColumn(
97                    Phone.LABEL));
98
99            kind.fieldList = Lists.newArrayList();
100            kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
101        }
102
103        return kind;
104    }
105
106    @Override
107    protected DataKind inflateEmail(Context context, int inflateLevel) {
108        final DataKind kind = super.inflateEmail(context, ContactsSource.LEVEL_MIMETYPES);
109
110        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
111            kind.typeColumn = Email.TYPE;
112            kind.typeList = Lists.newArrayList();
113            kind.typeList.add(buildEmailType(Email.TYPE_HOME));
114            kind.typeList.add(buildEmailType(Email.TYPE_WORK));
115            kind.typeList.add(buildEmailType(Email.TYPE_OTHER));
116            kind.typeList.add(buildEmailType(Email.TYPE_CUSTOM).setSecondary(true).setCustomColumn(
117                    Email.LABEL));
118
119            kind.fieldList = Lists.newArrayList();
120            kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
121        }
122
123        return kind;
124    }
125
126    @Override
127    protected DataKind inflateStructuredPostal(Context context, int inflateLevel) {
128        return super.inflateStructuredPostal(context, inflateLevel);
129    }
130
131    @Override
132    protected DataKind inflateIm(Context context, int inflateLevel) {
133        return super.inflateIm(context, inflateLevel);
134    }
135
136    @Override
137    protected DataKind inflateOrganization(Context context, int inflateLevel) {
138        return super.inflateOrganization(context, inflateLevel);
139    }
140
141    @Override
142    protected DataKind inflatePhoto(Context context, int inflateLevel) {
143        return super.inflatePhoto(context, inflateLevel);
144    }
145
146    @Override
147    protected DataKind inflateNote(Context context, int inflateLevel) {
148        return super.inflateNote(context, inflateLevel);
149    }
150
151    @Override
152    protected DataKind inflateWebsite(Context context, int inflateLevel) {
153        return super.inflateWebsite(context, inflateLevel);
154    }
155
156    // TODO: this should come from resource in the future
157    // Note that frameworks/base/core/java/android/pim/vcard/ContactStruct.java also wants
158    // this String.
159    private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts";
160
161    public static final void attemptMyContactsMembership(EntityDelta state, Context context) {
162        final ValuesDelta stateValues = state.getValues();
163	stateValues.setFromTemplate(true);
164        final String accountName = stateValues.getAsString(RawContacts.ACCOUNT_NAME);
165        final String accountType = stateValues.getAsString(RawContacts.ACCOUNT_TYPE);
166        attemptMyContactsMembership(state, accountName, accountType, context, true);
167    }
168
169    public static final void createMyContactsIfNotExist(Account account, Context context) {
170        attemptMyContactsMembership(null, account.name, account.type, context, true);
171    }
172
173    /**
174     *
175     * @param allowRecur If the group is created between querying/about to create, we recur.  But
176     *     to prevent excess recursion, we provide a flag to make sure we only do the recursion loop
177     *     once
178     */
179    private static final void attemptMyContactsMembership(EntityDelta state,
180                final String accountName, final String accountType, Context context,
181                boolean allowRecur) {
182        final ContentResolver resolver = context.getContentResolver();
183
184        Cursor cursor = resolver.query(Groups.CONTENT_URI,
185                new String[] {Groups.TITLE, Groups.SOURCE_ID, Groups.SHOULD_SYNC},
186                Groups.ACCOUNT_NAME + " =? AND " + Groups.ACCOUNT_TYPE + " =?",
187                new String[] {accountName, accountType}, null);
188
189        boolean myContactsExists = false;
190        long assignToGroupSourceId = -1;
191        while (cursor.moveToNext()) {
192            if (GOOGLE_MY_CONTACTS_GROUP.equals(cursor.getString(0))) {
193                myContactsExists = true;
194            }
195            if (assignToGroupSourceId == -1 && cursor.getInt(2) != 0) {
196                assignToGroupSourceId = cursor.getInt(1);
197            }
198
199            if (myContactsExists && assignToGroupSourceId != -1) {
200                break;
201            }
202        }
203
204        if (myContactsExists && state == null) {
205            return;
206        }
207
208        try {
209            final ContentValues values = new ContentValues();
210            values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
211
212            if (!myContactsExists) {
213                // create the group if it doesn't exist
214                final ContentValues newGroup = new ContentValues();
215                newGroup.put(Groups.TITLE, GOOGLE_MY_CONTACTS_GROUP);
216
217                newGroup.put(Groups.ACCOUNT_NAME, accountName);
218                newGroup.put(Groups.ACCOUNT_TYPE, accountType);
219                newGroup.put(Groups.GROUP_VISIBLE, "1");
220
221                ArrayList<ContentProviderOperation> operations =
222                    new ArrayList<ContentProviderOperation>();
223
224                operations.add(ContentProviderOperation
225                        .newAssertQuery(Groups.CONTENT_URI)
226                        .withSelection(Groups.TITLE + "=?",
227                                new String[] { GOOGLE_MY_CONTACTS_GROUP })
228                        .withExpectedCount(0).build());
229                operations.add(ContentProviderOperation
230
231                        .newInsert(Groups.CONTENT_URI)
232                        .withValues(newGroup)
233                        .build());
234                try {
235                    ContentProviderResult[] results = resolver.applyBatch(
236                            ContactsContract.AUTHORITY, operations);
237                    values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(results[1].uri));
238                } catch (RemoteException e) {
239                    throw new IllegalStateException("Problem querying for groups", e);
240                } catch (OperationApplicationException e) {
241                    // the group was created after the query but before we tried to create it
242                    if (allowRecur) {
243                        attemptMyContactsMembership(
244                                state, accountName, accountType, context, false);
245                    }
246                    return;
247                }
248            } else {
249                if (assignToGroupSourceId != -1) {
250                    values.put(GroupMembership.GROUP_SOURCE_ID, assignToGroupSourceId);
251                } else {
252                    // there are no Groups to add this contact to, so don't apply any membership
253                    // TODO: alert user that their contact will be dropped?
254                }
255            }
256            if (state != null) {
257                state.addEntry(ValuesDelta.fromAfter(values));
258            }
259        } finally {
260            cursor.close();
261        }
262    }
263
264    @Override
265    public int getHeaderColor(Context context) {
266        return 0xff89c2c2;
267    }
268
269    @Override
270    public int getSideBarColor(Context context) {
271        return 0xff5bb4b4;
272    }
273}
274