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 
17 package com.android.loaderapp.model;
18 
19 import com.android.loaderapp.R;
20 import com.android.loaderapp.model.EntityDelta.ValuesDelta;
21 import com.google.android.collect.Lists;
22 
23 import android.accounts.Account;
24 import android.content.ContentProviderOperation;
25 import android.content.ContentProviderResult;
26 import android.content.ContentResolver;
27 import android.content.ContentUris;
28 import android.content.ContentValues;
29 import android.content.Context;
30 import android.content.OperationApplicationException;
31 import android.database.Cursor;
32 import android.os.RemoteException;
33 import android.provider.ContactsContract;
34 import android.provider.ContactsContract.Groups;
35 import android.provider.ContactsContract.RawContacts;
36 import android.provider.ContactsContract.CommonDataKinds.Email;
37 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
38 import android.provider.ContactsContract.CommonDataKinds.Phone;
39 import android.provider.ContactsContract.Contacts.Data;
40 
41 import java.util.ArrayList;
42 
43 public class GoogleSource extends FallbackSource {
44     public static final String ACCOUNT_TYPE = "com.google";
GoogleSource(String resPackageName)45     public GoogleSource(String resPackageName) {
46         this.accountType = ACCOUNT_TYPE;
47         this.resPackageName = null;
48         this.summaryResPackageName = resPackageName;
49     }
50 
51     @Override
inflate(Context context, int inflateLevel)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
inflateStructuredName(Context context, int inflateLevel)73     protected DataKind inflateStructuredName(Context context, int inflateLevel) {
74         return super.inflateStructuredName(context, inflateLevel);
75     }
76 
77     @Override
inflateNickname(Context context, int inflateLevel)78     protected DataKind inflateNickname(Context context, int inflateLevel) {
79         return super.inflateNickname(context, inflateLevel);
80     }
81 
82     @Override
inflatePhone(Context context, int inflateLevel)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
inflateEmail(Context context, int inflateLevel)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
inflateStructuredPostal(Context context, int inflateLevel)127     protected DataKind inflateStructuredPostal(Context context, int inflateLevel) {
128         return super.inflateStructuredPostal(context, inflateLevel);
129     }
130 
131     @Override
inflateIm(Context context, int inflateLevel)132     protected DataKind inflateIm(Context context, int inflateLevel) {
133         return super.inflateIm(context, inflateLevel);
134     }
135 
136     @Override
inflateOrganization(Context context, int inflateLevel)137     protected DataKind inflateOrganization(Context context, int inflateLevel) {
138         return super.inflateOrganization(context, inflateLevel);
139     }
140 
141     @Override
inflatePhoto(Context context, int inflateLevel)142     protected DataKind inflatePhoto(Context context, int inflateLevel) {
143         return super.inflatePhoto(context, inflateLevel);
144     }
145 
146     @Override
inflateNote(Context context, int inflateLevel)147     protected DataKind inflateNote(Context context, int inflateLevel) {
148         return super.inflateNote(context, inflateLevel);
149     }
150 
151     @Override
inflateWebsite(Context context, int inflateLevel)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 
attemptMyContactsMembership(EntityDelta state, Context context)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 
createMyContactsIfNotExist(Account account, Context context)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      */
attemptMyContactsMembership(EntityDelta state, final String accountName, final String accountType, Context context, boolean allowRecur)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
getHeaderColor(Context context)265     public int getHeaderColor(Context context) {
266         return 0xff89c2c2;
267     }
268 
269     @Override
getSideBarColor(Context context)270     public int getSideBarColor(Context context) {
271         return 0xff5bb4b4;
272     }
273 }
274