public void testTrimLeaveValid() {
    final ContactsSource source = getSource();
    final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);

    // Test row that has type values with valid number
    final EntityDelta state = EntitySetTests.buildBeforeEntity(TEST_ID, VER_FIRST);
    final ValuesDelta values = EntityModifier.insertChild(state, kindPhone, typeHome);
    values.put(Phone.NUMBER, TEST_PHONE);

    // Build diff, expecting insert for data row and update enforcement
    EntitySetTests.assertDiffPattern(
        state,
        EntitySetTests.buildAssertVersion(VER_FIRST),
        EntitySetTests.buildUpdateAggregationSuspended(),
        EntitySetTests.buildOper(
            Data.CONTENT_URI, TYPE_INSERT, EntitySetTests.buildDataInsert(values, TEST_ID)),
        EntitySetTests.buildUpdateAggregationDefault());

    // Trim empty rows and try again, expecting no differences
    EntityModifier.trimEmpty(state, source);
    EntitySetTests.assertDiffPattern(
        state,
        EntitySetTests.buildAssertVersion(VER_FIRST),
        EntitySetTests.buildUpdateAggregationSuspended(),
        EntitySetTests.buildOper(
            Data.CONTENT_URI, TYPE_INSERT, EntitySetTests.buildDataInsert(values, TEST_ID)),
        EntitySetTests.buildUpdateAggregationDefault());
  }
  public void testTrimInsertInsert() {
    final ContactsSource source = getSource();
    final Sources sources = getSources(source);
    final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);

    // Try creating a contact with single empty entry
    final EntityDelta state = getEntity(null);
    final ValuesDelta values = EntityModifier.insertChild(state, kindPhone, typeHome);
    final EntitySet set = EntitySet.fromSingle(state);

    // Build diff, expecting two insert operations
    final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
    state.buildDiff(diff);
    assertEquals("Unexpected operations", 3, diff.size());
    {
      final ContentProviderOperation oper = diff.get(0);
      assertEquals("Incorrect type", TYPE_INSERT, oper.getType());
      assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    }
    {
      final ContentProviderOperation oper = diff.get(1);
      assertEquals("Incorrect type", TYPE_INSERT, oper.getType());
      assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
    }

    // Trim empty rows and try again, expecting silence
    EntityModifier.trimEmpty(set, sources);
    diff.clear();
    state.buildDiff(diff);
    assertEquals("Unexpected operations", 0, diff.size());
  }
  public void testIsEmptyEmpty() {
    final ContactsSource source = getSource();
    final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);

    // Test entirely empty row
    final ContentValues after = new ContentValues();
    final ValuesDelta values = ValuesDelta.fromAfter(after);

    assertTrue("Expected empty", EntityModifier.isEmpty(values, kindPhone));
  }
  public void testTrimUpdateUpdate() {
    final ContactsSource source = getSource();
    final Sources sources = getSources(source);
    final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);

    // Build "before" with two phone numbers
    final ContentValues first = new ContentValues();
    first.put(Data._ID, TEST_ID);
    first.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
    first.put(kindPhone.typeColumn, typeHome.rawValue);
    first.put(Phone.NUMBER, TEST_PHONE);

    final EntityDelta state = getEntity(TEST_ID, first);
    final EntitySet set = EntitySet.fromSingle(state);

    // Build diff, expecting no changes
    final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
    state.buildDiff(diff);
    assertEquals("Unexpected operations", 0, diff.size());

    // Now update row by changing number to empty string, expecting single update
    final ValuesDelta child = state.getEntry(TEST_ID);
    child.put(Phone.NUMBER, "");
    diff.clear();
    state.buildDiff(diff);
    assertEquals("Unexpected operations", 3, diff.size());
    {
      final ContentProviderOperation oper = diff.get(0);
      assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
      assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    }
    {
      final ContentProviderOperation oper = diff.get(1);
      assertEquals("Incorrect type", TYPE_UPDATE, oper.getType());
      assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
    }
    {
      final ContentProviderOperation oper = diff.get(2);
      assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
      assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    }

    // Now run trim, which should turn into deleting the whole contact
    EntityModifier.trimEmpty(set, sources);
    diff.clear();
    state.buildDiff(diff);
    assertEquals("Unexpected operations", 1, diff.size());
    {
      final ContentProviderOperation oper = diff.get(0);
      assertEquals("Incorrect type", TYPE_DELETE, oper.getType());
      assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    }
  }
  public void testIsEmptyDirectFields() {
    final ContactsSource source = getSource();
    final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);

    // Test row that has type values, but core fields are empty
    final EntityDelta state = getEntity(TEST_ID);
    final ValuesDelta values = EntityModifier.insertChild(state, kindPhone, typeHome);

    assertTrue("Expected empty", EntityModifier.isEmpty(values, kindPhone));

    // Insert some data to trigger non-empty state
    values.put(Phone.NUMBER, TEST_PHONE);

    assertFalse("Expected non-empty", EntityModifier.isEmpty(values, kindPhone));
  }
  public void testParseExtrasExistingName() {
    final ContactsSource source = getSource();
    final DataKind kindName = source.getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE);

    // Build "before" name
    final ContentValues first = new ContentValues();
    first.put(Data._ID, TEST_ID);
    first.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
    first.put(StructuredName.GIVEN_NAME, TEST_NAME);

    // Parse extras, making sure we keep single name
    final EntityDelta state = getEntity(TEST_ID, first);
    final Bundle extras = new Bundle();
    extras.putString(Insert.NAME, TEST_NAME2);
    EntityModifier.parseExtras(mContext, source, state, extras);

    final int nameCount = state.getMimeEntriesCount(StructuredName.CONTENT_ITEM_TYPE, true);
    assertEquals("Unexpected names", 1, nameCount);
  }
  public void testTrimEmptySingle() {
    final ContactsSource source = getSource();
    final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);

    // Test row that has type values, but core fields are empty
    final EntityDelta state = getEntity(TEST_ID);
    final ValuesDelta values = EntityModifier.insertChild(state, kindPhone, typeHome);

    // Build diff, expecting insert for data row and update enforcement
    final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
    state.buildDiff(diff);
    assertEquals("Unexpected operations", 3, diff.size());
    {
      final ContentProviderOperation oper = diff.get(0);
      assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
      assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    }
    {
      final ContentProviderOperation oper = diff.get(1);
      assertEquals("Incorrect type", TYPE_INSERT, oper.getType());
      assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
    }
    {
      final ContentProviderOperation oper = diff.get(2);
      assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType());
      assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    }

    // Trim empty rows and try again, expecting delete of overall contact
    EntityModifier.trimEmpty(state, source);
    diff.clear();
    state.buildDiff(diff);
    assertEquals("Unexpected operations", 1, diff.size());
    {
      final ContentProviderOperation oper = diff.get(0);
      assertEquals("Incorrect type", TYPE_DELETE, oper.getType());
      assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    }
  }
  /**
   * Insert various rows to test {@link EntityModifier#getValidTypes(EntityDelta, DataKind,
   * EditType)}
   */
  public void testValidTypes() {
    // Build a source and pull specific types
    final ContactsSource source = getSource();
    final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
    final EditType typeWork = EntityModifier.getType(kindPhone, Phone.TYPE_WORK);
    final EditType typeOther = EntityModifier.getType(kindPhone, Phone.TYPE_OTHER);

    List<EditType> validTypes;

    // Add first home, first work
    final EntityDelta state = getEntity(TEST_ID);
    EntityModifier.insertChild(state, kindPhone, typeHome);
    EntityModifier.insertChild(state, kindPhone, typeWork);

    // Expecting home, other
    validTypes = EntityModifier.getValidTypes(state, kindPhone, null);
    assertContains(validTypes, typeHome);
    assertNotContains(validTypes, typeWork);
    assertContains(validTypes, typeOther);

    // Add second home
    EntityModifier.insertChild(state, kindPhone, typeHome);

    // Expecting other
    validTypes = EntityModifier.getValidTypes(state, kindPhone, null);
    assertNotContains(validTypes, typeHome);
    assertNotContains(validTypes, typeWork);
    assertContains(validTypes, typeOther);

    // Add third and fourth home (invalid, but possible)
    EntityModifier.insertChild(state, kindPhone, typeHome);
    EntityModifier.insertChild(state, kindPhone, typeHome);

    // Expecting none
    validTypes = EntityModifier.getValidTypes(state, kindPhone, null);
    assertNotContains(validTypes, typeHome);
    assertNotContains(validTypes, typeWork);
    assertNotContains(validTypes, typeOther);
  }
  public void testParseExtrasIgnoreLimit() {
    final ContactsSource source = getSource();
    final DataKind kindIm = source.getKindForMimetype(Im.CONTENT_ITEM_TYPE);

    // Build "before" IM
    final ContentValues first = new ContentValues();
    first.put(Data._ID, TEST_ID);
    first.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
    first.put(Im.DATA, TEST_IM);

    final EntityDelta state = getEntity(TEST_ID, first);
    final int beforeCount = state.getMimeEntries(Im.CONTENT_ITEM_TYPE).size();

    // We should ignore data that doesn't fit source rules, since source
    // only allows single Im
    final Bundle extras = new Bundle();
    extras.putInt(Insert.IM_PROTOCOL, Im.PROTOCOL_GOOGLE_TALK);
    extras.putString(Insert.IM_HANDLE, TEST_IM);
    EntityModifier.parseExtras(mContext, source, state, extras);

    final int afterCount = state.getMimeEntries(Im.CONTENT_ITEM_TYPE).size();
    assertEquals("Broke source rules", beforeCount, afterCount);
  }
  public void testTrimEmptyUntouched() {
    final ContactsSource source = getSource();
    final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);

    // Build "before" that has empty row
    final EntityDelta state = getEntity(TEST_ID);
    final ContentValues before = new ContentValues();
    before.put(Data._ID, TEST_ID);
    before.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
    state.addEntry(ValuesDelta.fromBefore(before));

    // Build diff, expecting no changes
    final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
    state.buildDiff(diff);
    assertEquals("Unexpected operations", 0, diff.size());

    // Try trimming existing empty, which we shouldn't touch
    EntityModifier.trimEmpty(state, source);
    diff.clear();
    state.buildDiff(diff);
    assertEquals("Unexpected operations", 0, diff.size());
  }
  /** Test {@link EntityModifier#canInsert(EntityDelta, DataKind)} by inserting various rows. */
  public void testCanInsert() {
    // Build a source and pull specific types
    final ContactsSource source = getSource();
    final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
    final EditType typeWork = EntityModifier.getType(kindPhone, Phone.TYPE_WORK);
    final EditType typeOther = EntityModifier.getType(kindPhone, Phone.TYPE_OTHER);

    // Add first home, first work
    final EntityDelta state = getEntity(TEST_ID);
    EntityModifier.insertChild(state, kindPhone, typeHome);
    EntityModifier.insertChild(state, kindPhone, typeWork);
    assertTrue("Unable to insert", EntityModifier.canInsert(state, kindPhone));

    // Add two other, which puts us just under "5" overall limit
    EntityModifier.insertChild(state, kindPhone, typeOther);
    EntityModifier.insertChild(state, kindPhone, typeOther);
    assertTrue("Unable to insert", EntityModifier.canInsert(state, kindPhone));

    // Add second home, which should push to snug limit
    EntityModifier.insertChild(state, kindPhone, typeHome);
    assertFalse("Able to insert", EntityModifier.canInsert(state, kindPhone));
  }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
      View view = super.getView(position, convertView, parent);

      PhoneItem item = getItem(position);
      ContactsSource source =
          mSources.getInflatedSource(item.accountType, ContactsSource.LEVEL_SUMMARY);

      // Obtain a string representation of the phone type specific to the
      // ContactSource associated with that phone number
      TextView typeView = (TextView) view.findViewById(android.R.id.text1);
      DataKind kind = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
      if (kind != null) {
        ContentValues values = new ContentValues();
        values.put(Phone.TYPE, item.type);
        values.put(Phone.LABEL, item.label);
        StringInflater header = sendSms ? kind.actionAltHeader : kind.actionHeader;
        typeView.setText(header.inflateUsing(getContext(), values));
      } else {
        typeView.setText(R.string.call_other);
      }
      return view;
    }
  /**
   * Test {@link EntityModifier#getBestValidType(EntityDelta, DataKind, boolean, int)} by asserting
   * expected best options in various states.
   */
  public void testBestValidType() {
    // Build a source and pull specific types
    final ContactsSource source = getSource();
    final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
    final EditType typeWork = EntityModifier.getType(kindPhone, Phone.TYPE_WORK);
    final EditType typeFaxWork = EntityModifier.getType(kindPhone, Phone.TYPE_FAX_WORK);
    final EditType typeOther = EntityModifier.getType(kindPhone, Phone.TYPE_OTHER);

    EditType suggested;

    // Default suggestion should be home
    final EntityDelta state = getEntity(TEST_ID);
    suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
    assertEquals("Unexpected suggestion", typeHome, suggested);

    // Add first home, should now suggest work
    EntityModifier.insertChild(state, kindPhone, typeHome);
    suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
    assertEquals("Unexpected suggestion", typeWork, suggested);

    // Add work fax, should still suggest work
    EntityModifier.insertChild(state, kindPhone, typeFaxWork);
    suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
    assertEquals("Unexpected suggestion", typeWork, suggested);

    // Add other, should still suggest work
    EntityModifier.insertChild(state, kindPhone, typeOther);
    suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
    assertEquals("Unexpected suggestion", typeWork, suggested);

    // Add work, now should suggest other
    EntityModifier.insertChild(state, kindPhone, typeWork);
    suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
    assertEquals("Unexpected suggestion", typeOther, suggested);
  }
  /**
   * Set the internal state for this view, given a current {@link EntityDelta} state and the {@link
   * ContactsSource} that apply to that state.
   *
   * <p>TODO: make this more generic using data from the source
   */
  @Override
  public void setState(EntityDelta state, ContactsSource source, ViewIdGenerator vig) {
    // Remove any existing sections
    mGeneral.removeAllViews();

    // Bail if invalid state or source
    if (state == null || source == null) return;

    // Make sure we have StructuredName
    EntityModifier.ensureKindExists(state, source, StructuredName.CONTENT_ITEM_TYPE);

    // Fill in the header info
    ValuesDelta values = state.getValues();
    String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
    CharSequence accountType = source.getDisplayLabel(mContext);
    if (TextUtils.isEmpty(accountType)) {
      accountType = mContext.getString(R.string.account_phone);
    }
    if (!TextUtils.isEmpty(accountName)) {
      mHeaderAccountName.setText(mContext.getString(R.string.from_account_format, accountName));
    }
    mHeaderAccountType.setText(mContext.getString(R.string.account_type_format, accountType));
    mHeaderIcon.setImageDrawable(source.getDisplayIcon(mContext));

    mRawContactId = values.getAsLong(RawContacts._ID);

    ValuesDelta primary;

    // Photo
    DataKind kind = source.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
    if (kind != null) {
      EntityModifier.ensureKindExists(state, source, Photo.CONTENT_ITEM_TYPE);
      mHasPhotoEditor = (source.getKindForMimetype(Photo.CONTENT_ITEM_TYPE) != null);
      primary = state.getPrimaryEntry(Photo.CONTENT_ITEM_TYPE);
      mPhoto.setValues(kind, primary, state, source.readOnly, vig);
      if (!mHasPhotoEditor || !mPhoto.hasSetPhoto()) {
        mPhotoStub.setVisibility(View.GONE);
      } else {
        mPhotoStub.setVisibility(View.VISIBLE);
      }
    } else {
      mPhotoStub.setVisibility(View.VISIBLE);
    }

    // Name
    primary = state.getPrimaryEntry(StructuredName.CONTENT_ITEM_TYPE);
    mName.setText(primary.getAsString(StructuredName.DISPLAY_NAME));

    // Read only warning
    mReadOnlyWarning.setText(mContext.getString(R.string.contact_read_only, accountType));

    // Phones
    ArrayList<ValuesDelta> phones = state.getMimeEntries(Phone.CONTENT_ITEM_TYPE);
    if (phones != null) {
      for (ValuesDelta phone : phones) {
        View field = mInflater.inflate(R.layout.item_read_only_field, mGeneral, false);
        TextView v;
        v = (TextView) field.findViewById(R.id.label);
        v.setText(mContext.getText(R.string.phoneLabelsGroup));
        v = (TextView) field.findViewById(R.id.data);
        v.setText(PhoneNumberFormatUtilEx.formatNumber(phone.getAsString(Phone.NUMBER)));
        mGeneral.addView(field);
      }
    }

    // Emails
    ArrayList<ValuesDelta> emails = state.getMimeEntries(Email.CONTENT_ITEM_TYPE);
    if (emails != null) {
      for (ValuesDelta email : emails) {
        View field = mInflater.inflate(R.layout.item_read_only_field, mGeneral, false);
        TextView v;
        v = (TextView) field.findViewById(R.id.label);
        v.setText(mContext.getText(R.string.emailLabelsGroup));
        v = (TextView) field.findViewById(R.id.data);
        v.setText(email.getAsString(Email.DATA));
        mGeneral.addView(field);
      }
    }

    // Hide mGeneral if it's empty
    if (mGeneral.getChildCount() > 0) {
      mGeneral.setVisibility(View.VISIBLE);
    } else {
      mGeneral.setVisibility(View.GONE);
    }
  }
 /** Build a {@link ContactsSource} that has various odd constraints for testing purposes. */
 protected ContactsSource getSource() {
   final ContactsSource source = new MockContactsSource();
   source.ensureInflated(getContext(), ContactsSource.LEVEL_CONSTRAINTS);
   return source;
 }