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