/**
   * Create a <tt>SourceContact</tt> from a <tt>GoogleContactsEntry</tt>.
   *
   * @param entry <tt>GoogleContactsEntry</tt>
   */
  private void onGoogleContactsEntry(GoogleContactsEntry entry) {
    String displayName = entry.getFullName();
    if (displayName == null || displayName.length() == 0) {
      if ((entry.getGivenName() == null || entry.getGivenName().length() == 0)
          && (entry.getFamilyName() == null || entry.getFamilyName().length() == 0)) {
        return;
      }

      displayName = entry.getGivenName() + " " + entry.getFamilyName();
    }

    List<ContactDetail> contactDetails = getContactDetails(entry);

    if (!contactDetails.isEmpty()) {
      GenericSourceContact sourceContact =
          new GenericSourceContact(getContactSource(), displayName, contactDetails);

      try {
        byte img[] =
            GoogleContactsServiceImpl.downloadPhoto(
                ((GoogleContactsEntryImpl) entry).getPhotoLink(),
                getContactSource().getConnection().getGoogleService());
        sourceContact.setImage(img);
      } catch (OutOfMemoryError oome) {
        // Ignore it, the image is not vital.
      }

      addQueryResult(sourceContact);
    }
  }
  /**
   * Stops this <tt>ContactSourceService</tt> implementation and prepares it for garbage collection.
   *
   * @see AsyncContactSourceService#stop()
   */
  public void stop() {
    boolean interrupted = false;

    synchronized (queries) {
      while (!queries.isEmpty()) {
        queries.get(0).cancel();
        try {
          queries.wait();
        } catch (InterruptedException iex) {
          interrupted = true;
        }
      }
    }
    if (interrupted) Thread.currentThread().interrupt();
  }
  /**
   * Creates query for the given <tt>searchPattern</tt>.
   *
   * @param queryPattern the pattern to search for
   * @param count maximum number of contact returned
   * @return the created query
   */
  public ContactQuery createContactQuery(Pattern queryPattern, int count) {
    GoogleContactsQuery query = new GoogleContactsQuery(this, queryPattern, count);

    synchronized (queries) {
      queries.add(query);
    }

    return query;
  }
 /**
  * Notifies this <tt>GoogleContactsSourceService</tt> that a specific <tt>GoogleContactsQuery</tt>
  * has stopped.
  *
  * @param query the <tt>GoogleContactsQuery</tt> which has stopped
  */
 void stopped(GoogleContactsQuery query) {
   synchronized (queries) {
     if (queries.remove(query)) queries.notify();
   }
 }
 /**
  * Removes query from the list of queries.
  *
  * @param query the query that will be removed.
  */
 public synchronized void removeQuery(ContactQuery query) {
   if (queries.remove(query)) queries.notify();
 }
  /**
   * Gets the <tt>contactDetails</tt> to be set on a <tt>SourceContact</tt>.
   *
   * @param entry <tt>GoogleContactsEntry</tt>
   * @return the <tt>contactDetails</tt> to be set on a <tt>SourceContact</tt>
   */
  private List<ContactDetail> getContactDetails(GoogleContactsEntry entry) {
    List<ContactDetail> ret = new LinkedList<ContactDetail>();
    List<String> homeMails = entry.getHomeMails();
    List<String> workMails = entry.getWorkMails();
    List<String> mobilePhones = entry.getMobilePhones();
    List<String> homePhones = entry.getHomePhones();
    List<String> workPhones = entry.getWorkPhones();
    Map<String, GoogleContactsEntry.IMProtocol> ims = entry.getIMAddresses();
    ContactDetail detail = null;

    for (String mail : homeMails) {
      List<Class<? extends OperationSet>> supportedOpSets =
          new ArrayList<Class<? extends OperationSet>>(1);
      // can be added as contacts
      supportedOpSets.add(OperationSetPersistentPresence.class);

      detail =
          new ContactDetail(
              mail,
              ContactDetail.Category.Email,
              new ContactDetail.SubCategory[] {ContactDetail.SubCategory.Home});
      detail.setSupportedOpSets(supportedOpSets);
      ret.add(detail);
    }
    for (String mail : workMails) {
      List<Class<? extends OperationSet>> supportedOpSets =
          new ArrayList<Class<? extends OperationSet>>(1);
      // can be added as contacts
      supportedOpSets.add(OperationSetPersistentPresence.class);

      detail =
          new ContactDetail(
              mail,
              ContactDetail.Category.Email,
              new ContactDetail.SubCategory[] {ContactDetail.SubCategory.Work});
      detail.setSupportedOpSets(supportedOpSets);
      ret.add(detail);
    }

    for (String homePhone : homePhones) {
      List<Class<? extends OperationSet>> supportedOpSets =
          new ArrayList<Class<? extends OperationSet>>(2);

      supportedOpSets.add(OperationSetBasicTelephony.class);
      // can be added as contacts
      supportedOpSets.add(OperationSetPersistentPresence.class);
      homePhone = PhoneNumberI18nService.normalize(homePhone);
      detail =
          new ContactDetail(
              homePhone,
              ContactDetail.Category.Phone,
              new ContactDetail.SubCategory[] {ContactDetail.SubCategory.Home});
      detail.setSupportedOpSets(supportedOpSets);
      ret.add(detail);
    }

    for (String workPhone : workPhones) {
      List<Class<? extends OperationSet>> supportedOpSets =
          new ArrayList<Class<? extends OperationSet>>(2);

      supportedOpSets.add(OperationSetBasicTelephony.class);
      // can be added as contacts
      supportedOpSets.add(OperationSetPersistentPresence.class);
      workPhone = PhoneNumberI18nService.normalize(workPhone);
      detail =
          new ContactDetail(
              workPhone,
              ContactDetail.Category.Phone,
              new ContactDetail.SubCategory[] {ContactDetail.SubCategory.Work});
      detail.setSupportedOpSets(supportedOpSets);
      ret.add(detail);
    }

    for (String mobilePhone : mobilePhones) {
      List<Class<? extends OperationSet>> supportedOpSets =
          new ArrayList<Class<? extends OperationSet>>(2);

      supportedOpSets.add(OperationSetBasicTelephony.class);
      // can be added as contacts
      supportedOpSets.add(OperationSetPersistentPresence.class);
      mobilePhone = PhoneNumberI18nService.normalize(mobilePhone);
      detail =
          new ContactDetail(
              mobilePhone,
              ContactDetail.Category.Phone,
              new ContactDetail.SubCategory[] {ContactDetail.SubCategory.Mobile});
      detail.setSupportedOpSets(supportedOpSets);
      ret.add(detail);
    }

    for (Map.Entry<String, GoogleContactsEntry.IMProtocol> im : ims.entrySet()) {
      if (im.getValue() != GoogleContactsEntry.IMProtocol.OTHER) {
        ContactDetail.SubCategory imSubCat;
        switch (im.getValue()) {
          case AIM:
            imSubCat = ContactDetail.SubCategory.AIM;
            break;
          case ICQ:
            imSubCat = ContactDetail.SubCategory.ICQ;
            break;
          case YAHOO:
            imSubCat = ContactDetail.SubCategory.Yahoo;
            break;
          case JABBER:
            imSubCat = ContactDetail.SubCategory.Jabber;
            break;
          case MSN:
            imSubCat = ContactDetail.SubCategory.MSN;
            break;
          case GOOGLETALK:
            imSubCat = ContactDetail.SubCategory.GoogleTalk;
            break;
          default:
            imSubCat = null;
            break;
        }

        detail =
            new ContactDetail(
                im.getKey(),
                ContactDetail.Category.InstantMessaging,
                new ContactDetail.SubCategory[] {imSubCat});

        setIMCapabilities(detail, im.getValue());

        // can be added as contacts
        detail.getSupportedOperationSets().add(OperationSetPersistentPresence.class);

        ret.add(detail);
      }
    }

    return ret;
  }