Home | History | Annotate | Download | only in databasepopulator
      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