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 (at) 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 (at) 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 (at) 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 (at) example.com")) 124 .setIsStarred(true) 125 .setBluePhoto() 126 .build(), 127 }; 128 129 @WorkerThread 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 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[5], operations); 156 try { 157 context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations); 158 } catch (RemoteException | OperationApplicationException e) { 159 Assert.fail("error adding contacts: " + e); 160 } 161 } 162 163 @WorkerThread 164 public static void populateContacts(@NonNull Context context) { 165 populateContacts(context, false); 166 } 167 168 @WorkerThread 169 public static void deleteAllContacts(@NonNull Context context) { 170 Assert.isWorkerThread(); 171 try { 172 context 173 .getContentResolver() 174 .applyBatch( 175 ContactsContract.AUTHORITY, 176 new ArrayList<>( 177 Arrays.asList( 178 ContentProviderOperation.newDelete(RawContacts.CONTENT_URI).build()))); 179 } catch (RemoteException | OperationApplicationException e) { 180 Assert.fail("failed to delete contacts: " + e); 181 } 182 } 183 184 private static void addContact(Contact contact, List<ContentProviderOperation> operations) { 185 int index = operations.size(); 186 187 operations.add( 188 ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) 189 .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, contact.getAccountType()) 190 .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, contact.getAccountName()) 191 .withValue(ContactsContract.RawContacts.STARRED, contact.getIsStarred() ? 1 : 0) 192 .withValue( 193 ContactsContract.RawContacts.PINNED, 194 contact.getIsStarred() ? contact.getPinned() : 0) 195 .withYieldAllowed(true) 196 .build()); 197 198 if (!TextUtils.isEmpty(contact.getName())) { 199 operations.add( 200 ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) 201 .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index) 202 .withValue( 203 ContactsContract.Data.MIMETYPE, 204 ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) 205 .withValue( 206 ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, contact.getName()) 207 .build()); 208 } 209 210 if (contact.getPhotoStream() != null) { 211 operations.add( 212 ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) 213 .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index) 214 .withValue( 215 ContactsContract.Data.MIMETYPE, 216 ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE) 217 .withValue( 218 ContactsContract.CommonDataKinds.Photo.PHOTO, 219 contact.getPhotoStream().toByteArray()) 220 .build()); 221 } 222 223 for (PhoneNumber phoneNumber : contact.getPhoneNumbers()) { 224 operations.add( 225 ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) 226 .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index) 227 .withValue( 228 ContactsContract.Data.MIMETYPE, 229 ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) 230 .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber.value) 231 .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneNumber.type) 232 .withValue(ContactsContract.CommonDataKinds.Phone.LABEL, phoneNumber.label) 233 .build()); 234 } 235 236 for (Email email : contact.getEmails()) { 237 operations.add( 238 ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) 239 .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index) 240 .withValue( 241 ContactsContract.Data.MIMETYPE, 242 ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) 243 .withValue(ContactsContract.CommonDataKinds.Email.DATA, email.value) 244 .withValue(ContactsContract.CommonDataKinds.Email.TYPE, email.type) 245 .withValue(ContactsContract.CommonDataKinds.Email.LABEL, email.label) 246 .build()); 247 } 248 } 249 250 @AutoValue 251 abstract static class Contact { 252 @NonNull 253 abstract String getAccountType(); 254 255 @NonNull 256 abstract String getAccountName(); 257 258 @Nullable 259 abstract String getName(); 260 261 abstract boolean getIsStarred(); 262 263 abstract int getPinned(); 264 265 @Nullable 266 abstract ByteArrayOutputStream getPhotoStream(); 267 268 @NonNull 269 abstract List<PhoneNumber> getPhoneNumbers(); 270 271 @NonNull 272 abstract List<Email> getEmails(); 273 274 static Builder builder() { 275 return new AutoValue_ContactsPopulator_Contact.Builder() 276 .setAccountType("com.google") 277 .setAccountName("foo@example") 278 .setPinned(0) 279 .setIsStarred(false) 280 .setPhoneNumbers(new ArrayList<>()) 281 .setEmails(new ArrayList<>()); 282 } 283 284 @AutoValue.Builder 285 abstract static class Builder { 286 @NonNull private final List<PhoneNumber> phoneNumbers = new ArrayList<>(); 287 @NonNull private final List<Email> emails = new ArrayList<>(); 288 289 abstract Builder setAccountType(@NonNull String accountType); 290 291 abstract Builder setAccountName(@NonNull String accountName); 292 293 abstract Builder setName(@NonNull String name); 294 295 abstract Builder setIsStarred(boolean isStarred); 296 297 abstract Builder setPinned(int position); 298 299 abstract Builder setPhotoStream(ByteArrayOutputStream photoStream); 300 301 abstract Builder setPhoneNumbers(@NonNull List<PhoneNumber> phoneNumbers); 302 303 abstract Builder setEmails(@NonNull List<Email> emails); 304 305 abstract Contact build(); 306 307 Builder addPhoneNumber(PhoneNumber phoneNumber) { 308 phoneNumbers.add(phoneNumber); 309 return setPhoneNumbers(phoneNumbers); 310 } 311 312 Builder addEmail(Email email) { 313 emails.add(email); 314 return setEmails(emails); 315 } 316 317 Builder setRedPhoto() { 318 setPhotoStream(getPhotoStreamWithColor(Color.rgb(0xe3, 0x33, 0x1c))); 319 return this; 320 } 321 322 Builder setBluePhoto() { 323 setPhotoStream(getPhotoStreamWithColor(Color.rgb(0x00, 0xaa, 0xe6))); 324 return this; 325 } 326 327 Builder setOrangePhoto() { 328 setPhotoStream(getPhotoStreamWithColor(Color.rgb(0xea, 0x95, 0x00))); 329 return this; 330 } 331 332 Builder setPurplePhoto() { 333 setPhotoStream(getPhotoStreamWithColor(Color.rgb(0x99, 0x5a, 0xa0))); 334 return this; 335 } 336 337 /** Creates a contact photo with a green background and a circle of the given color. */ 338 private static ByteArrayOutputStream getPhotoStreamWithColor(int color) { 339 int width = 300; 340 int height = 300; 341 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 342 Canvas canvas = new Canvas(bitmap); 343 canvas.drawColor(Color.argb(0xff, 0x4c, 0x9c, 0x23)); 344 Paint paint = new Paint(); 345 paint.setColor(color); 346 paint.setStyle(Paint.Style.FILL); 347 canvas.drawCircle(width / 2, height / 2, width / 3, paint); 348 349 ByteArrayOutputStream photoStream = new ByteArrayOutputStream(); 350 bitmap.compress(Bitmap.CompressFormat.PNG, 75, photoStream); 351 return photoStream; 352 } 353 } 354 } 355 356 static class PhoneNumber { 357 public final String value; 358 public final int type; 359 public final String label; 360 361 PhoneNumber(String value, int type) { 362 this.value = value; 363 this.type = type; 364 label = "simulator phone number"; 365 } 366 } 367 368 static class Email { 369 public final String value; 370 public final int type; 371 public final String label; 372 373 Email(String simpleEmail) { 374 value = simpleEmail; 375 type = ContactsContract.CommonDataKinds.Email.TYPE_WORK; 376 label = "simulator email"; 377 } 378 } 379 380 private ContactsPopulator() {} 381 } 382