1 /* 2 * Copyright (C) 2017 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.dialer.databasepopulator; 18 19 import android.content.ContentProviderOperation; 20 import android.content.Context; 21 import android.content.OperationApplicationException; 22 import android.graphics.Bitmap; 23 import android.graphics.Canvas; 24 import android.graphics.Color; 25 import android.graphics.Paint; 26 import android.os.RemoteException; 27 import android.provider.ContactsContract; 28 import android.provider.ContactsContract.CommonDataKinds.Phone; 29 import android.provider.ContactsContract.RawContacts; 30 import android.support.annotation.NonNull; 31 import android.support.annotation.Nullable; 32 import android.support.annotation.WorkerThread; 33 import android.text.TextUtils; 34 import com.android.dialer.common.Assert; 35 import com.google.auto.value.AutoValue; 36 import java.io.ByteArrayOutputStream; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.List; 40 41 /** Populates the device database with contacts. */ 42 public final class ContactsPopulator { 43 // Phone numbers from https://www.google.com/about/company/facts/locations/ 44 private static final Contact[] SIMPLE_CONTACTS = { 45 // US, contact with e164 number. 46 Contact.builder() 47 .setName("Michelangelo") 48 .addPhoneNumber(new PhoneNumber("+1-302-6365454", Phone.TYPE_MOBILE)) 49 .addEmail(new Email("m@example.com")) 50 .setIsStarred(true) 51 .setPinned(1) 52 .setOrangePhoto() 53 .build(), 54 // US, contact with a non-e164 number. 55 Contact.builder() 56 .setName("Leonardo da Vinci") 57 .addPhoneNumber(new PhoneNumber("(425) 739-5600", Phone.TYPE_MOBILE)) 58 .addEmail(new Email("l@example.com")) 59 .setIsStarred(true) 60 .setPinned(2) 61 .setBluePhoto() 62 .build(), 63 // UK, number where the (0) should be dropped. 64 Contact.builder() 65 .setName("Raphael") 66 .addPhoneNumber(new PhoneNumber("+44 (0) 20 7031 3000", Phone.TYPE_MOBILE)) 67 .addEmail(new Email("r@example.com")) 68 .setIsStarred(true) 69 .setPinned(3) 70 .setRedPhoto() 71 .build(), 72 // US and Australia, contact with a long name and multiple phone numbers. 73 Contact.builder() 74 .setName("Donatello di Niccolò di Betto Bardi") 75 .addPhoneNumber(new PhoneNumber("+1-650-2530000", Phone.TYPE_HOME)) 76 .addPhoneNumber(new PhoneNumber("+1 404-487-9000", Phone.TYPE_WORK)) 77 .addPhoneNumber(new PhoneNumber("+61 2 9374 4001", Phone.TYPE_FAX_HOME)) 78 .setIsStarred(true) 79 .setPinned(4) 80 .setPurplePhoto() 81 .build(), 82 // US, phone number shared with another contact and 2nd phone number with wait and pause. 83 Contact.builder() 84 .setName("Splinter") 85 .addPhoneNumber(new PhoneNumber("+1-650-2530000", Phone.TYPE_HOME)) 86 .addPhoneNumber(new PhoneNumber("+1 303-245-0086;123,456", Phone.TYPE_WORK)) 87 .setBluePhoto() 88 .build(), 89 // France, number with Japanese name. 90 Contact.builder() 91 .setName("スパイク・スピーゲル") 92 .addPhoneNumber(new PhoneNumber("+33 (0)1 42 68 53 00", Phone.TYPE_MOBILE)) 93 .setBluePhoto() 94 .build(), 95 // Israel, RTL name and non-e164 number. 96 Contact.builder() 97 .setName("עקב אריה טברסק") 98 .addPhoneNumber(new PhoneNumber("+33 (0)1 42 68 53 00", Phone.TYPE_MOBILE)) 99 .setBluePhoto() 100 .build(), 101 // UAE, RTL name. 102 Contact.builder() 103 .setName("سلام دنیا") 104 .addPhoneNumber(new PhoneNumber("+971 4 4509500", Phone.TYPE_MOBILE)) 105 .setBluePhoto() 106 .build(), 107 // Brazil, contact with no name. 108 Contact.builder() 109 .addPhoneNumber(new PhoneNumber("+55-31-2128-6800", Phone.TYPE_MOBILE)) 110 .setBluePhoto() 111 .build(), 112 // Short number, contact with no name. 113 Contact.builder().addPhoneNumber(new PhoneNumber("611", Phone.TYPE_MOBILE)).build(), 114 // US, number with an anonymous prefix. 115 Contact.builder() 116 .setName("Anonymous") 117 .addPhoneNumber(new PhoneNumber("*86 512-343-5283", Phone.TYPE_MOBILE)) 118 .setBluePhoto() 119 .build(), 120 // None, contact with no phone number. 121 Contact.builder() 122 .setName("No Phone Number") 123 .addEmail(new Email("no@example.com")) 124 .setIsStarred(true) 125 .setBluePhoto() 126 .build(), 127 }; 128 129 @WorkerThread populateContacts(@onNull Context context, boolean fastMode)130 public static void populateContacts(@NonNull Context context, boolean fastMode) { 131 Assert.isWorkerThread(); 132 ArrayList<ContentProviderOperation> operations = new ArrayList<>(); 133 List<Contact> contacts = new ArrayList<>(); 134 if (fastMode) { 135 contacts.add(SIMPLE_CONTACTS[0]); 136 } else { 137 contacts = Arrays.asList(SIMPLE_CONTACTS); 138 } 139 for (Contact contact : contacts) { 140 addContact(contact, operations); 141 } 142 143 try { 144 context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations); 145 } catch (RemoteException | OperationApplicationException e) { 146 Assert.fail("error adding contacts: " + e); 147 } 148 } 149 150 @WorkerThread populateSpeedDialTestContacts(@onNull Context context)151 public static void populateSpeedDialTestContacts(@NonNull Context context) { 152 Assert.isWorkerThread(); 153 ArrayList<ContentProviderOperation> operations = new ArrayList<>(); 154 addContact(SIMPLE_CONTACTS[0], operations); 155 addContact(SIMPLE_CONTACTS[3], operations); 156 addContact(SIMPLE_CONTACTS[5], operations); 157 try { 158 context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations); 159 } catch (RemoteException | OperationApplicationException e) { 160 Assert.fail("error adding contacts: " + e); 161 } 162 } 163 164 @WorkerThread populateContacts(@onNull Context context)165 public static void populateContacts(@NonNull Context context) { 166 populateContacts(context, false); 167 } 168 169 @WorkerThread deleteAllContacts(@onNull Context context)170 public static void deleteAllContacts(@NonNull Context context) { 171 Assert.isWorkerThread(); 172 try { 173 context 174 .getContentResolver() 175 .applyBatch( 176 ContactsContract.AUTHORITY, 177 new ArrayList<>( 178 Arrays.asList( 179 ContentProviderOperation.newDelete(RawContacts.CONTENT_URI).build()))); 180 } catch (RemoteException | OperationApplicationException e) { 181 Assert.fail("failed to delete contacts: " + e); 182 } 183 } 184 addContact(Contact contact, List<ContentProviderOperation> operations)185 private static void addContact(Contact contact, List<ContentProviderOperation> operations) { 186 int index = operations.size(); 187 188 operations.add( 189 ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) 190 .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, contact.getAccountType()) 191 .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, contact.getAccountName()) 192 .withValue(ContactsContract.RawContacts.STARRED, contact.getIsStarred() ? 1 : 0) 193 .withValue( 194 ContactsContract.RawContacts.PINNED, 195 contact.getIsStarred() ? contact.getPinned() : 0) 196 .withYieldAllowed(true) 197 .build()); 198 199 if (!TextUtils.isEmpty(contact.getName())) { 200 operations.add( 201 ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) 202 .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index) 203 .withValue( 204 ContactsContract.Data.MIMETYPE, 205 ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) 206 .withValue( 207 ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, contact.getName()) 208 .build()); 209 } 210 211 if (contact.getPhotoStream() != null) { 212 operations.add( 213 ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) 214 .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index) 215 .withValue( 216 ContactsContract.Data.MIMETYPE, 217 ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE) 218 .withValue( 219 ContactsContract.CommonDataKinds.Photo.PHOTO, 220 contact.getPhotoStream().toByteArray()) 221 .build()); 222 } 223 224 for (PhoneNumber phoneNumber : contact.getPhoneNumbers()) { 225 operations.add( 226 ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) 227 .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index) 228 .withValue( 229 ContactsContract.Data.MIMETYPE, 230 ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) 231 .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber.value) 232 .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneNumber.type) 233 .withValue(ContactsContract.CommonDataKinds.Phone.LABEL, phoneNumber.label) 234 .build()); 235 } 236 237 for (Email email : contact.getEmails()) { 238 operations.add( 239 ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) 240 .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index) 241 .withValue( 242 ContactsContract.Data.MIMETYPE, 243 ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) 244 .withValue(ContactsContract.CommonDataKinds.Email.DATA, email.value) 245 .withValue(ContactsContract.CommonDataKinds.Email.TYPE, email.type) 246 .withValue(ContactsContract.CommonDataKinds.Email.LABEL, email.label) 247 .build()); 248 } 249 } 250 251 @AutoValue 252 abstract static class Contact { 253 @NonNull getAccountType()254 abstract String getAccountType(); 255 256 @NonNull getAccountName()257 abstract String getAccountName(); 258 259 @Nullable getName()260 abstract String getName(); 261 getIsStarred()262 abstract boolean getIsStarred(); 263 getPinned()264 abstract int getPinned(); 265 266 @Nullable getPhotoStream()267 abstract ByteArrayOutputStream getPhotoStream(); 268 269 @NonNull getPhoneNumbers()270 abstract List<PhoneNumber> getPhoneNumbers(); 271 272 @NonNull getEmails()273 abstract List<Email> getEmails(); 274 builder()275 static Builder builder() { 276 return new AutoValue_ContactsPopulator_Contact.Builder() 277 .setAccountType("com.google") 278 .setAccountName("foo@example") 279 .setPinned(0) 280 .setIsStarred(false) 281 .setPhoneNumbers(new ArrayList<>()) 282 .setEmails(new ArrayList<>()); 283 } 284 285 @AutoValue.Builder 286 abstract static class Builder { 287 @NonNull private final List<PhoneNumber> phoneNumbers = new ArrayList<>(); 288 @NonNull private final List<Email> emails = new ArrayList<>(); 289 setAccountType(@onNull String accountType)290 abstract Builder setAccountType(@NonNull String accountType); 291 setAccountName(@onNull String accountName)292 abstract Builder setAccountName(@NonNull String accountName); 293 setName(@onNull String name)294 abstract Builder setName(@NonNull String name); 295 setIsStarred(boolean isStarred)296 abstract Builder setIsStarred(boolean isStarred); 297 setPinned(int position)298 abstract Builder setPinned(int position); 299 setPhotoStream(ByteArrayOutputStream photoStream)300 abstract Builder setPhotoStream(ByteArrayOutputStream photoStream); 301 setPhoneNumbers(@onNull List<PhoneNumber> phoneNumbers)302 abstract Builder setPhoneNumbers(@NonNull List<PhoneNumber> phoneNumbers); 303 setEmails(@onNull List<Email> emails)304 abstract Builder setEmails(@NonNull List<Email> emails); 305 build()306 abstract Contact build(); 307 addPhoneNumber(PhoneNumber phoneNumber)308 Builder addPhoneNumber(PhoneNumber phoneNumber) { 309 phoneNumbers.add(phoneNumber); 310 return setPhoneNumbers(phoneNumbers); 311 } 312 addEmail(Email email)313 Builder addEmail(Email email) { 314 emails.add(email); 315 return setEmails(emails); 316 } 317 setRedPhoto()318 Builder setRedPhoto() { 319 setPhotoStream(getPhotoStreamWithColor(Color.rgb(0xe3, 0x33, 0x1c))); 320 return this; 321 } 322 setBluePhoto()323 Builder setBluePhoto() { 324 setPhotoStream(getPhotoStreamWithColor(Color.rgb(0x00, 0xaa, 0xe6))); 325 return this; 326 } 327 setOrangePhoto()328 Builder setOrangePhoto() { 329 setPhotoStream(getPhotoStreamWithColor(Color.rgb(0xea, 0x95, 0x00))); 330 return this; 331 } 332 setPurplePhoto()333 Builder setPurplePhoto() { 334 setPhotoStream(getPhotoStreamWithColor(Color.rgb(0x99, 0x5a, 0xa0))); 335 return this; 336 } 337 338 /** Creates a contact photo with a green background and a circle of the given color. */ getPhotoStreamWithColor(int color)339 private static ByteArrayOutputStream getPhotoStreamWithColor(int color) { 340 int width = 300; 341 int height = 300; 342 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 343 Canvas canvas = new Canvas(bitmap); 344 canvas.drawColor(Color.argb(0xff, 0x4c, 0x9c, 0x23)); 345 Paint paint = new Paint(); 346 paint.setColor(color); 347 paint.setStyle(Paint.Style.FILL); 348 canvas.drawCircle(width / 2, height / 2, width / 3, paint); 349 350 ByteArrayOutputStream photoStream = new ByteArrayOutputStream(); 351 bitmap.compress(Bitmap.CompressFormat.PNG, 75, photoStream); 352 return photoStream; 353 } 354 } 355 } 356 357 static class PhoneNumber { 358 public final String value; 359 public final int type; 360 public final String label; 361 PhoneNumber(String value, int type)362 PhoneNumber(String value, int type) { 363 this.value = value; 364 this.type = type; 365 label = "simulator phone number"; 366 } 367 } 368 369 static class Email { 370 public final String value; 371 public final int type; 372 public final String label; 373 Email(String simpleEmail)374 Email(String simpleEmail) { 375 value = simpleEmail; 376 type = ContactsContract.CommonDataKinds.Email.TYPE_WORK; 377 label = "simulator email"; 378 } 379 } 380 ContactsPopulator()381 private ContactsPopulator() {} 382 } 383