protected DataKind inflateEmail(int inflateLevel) {
    DataKind kind = getKindForMimetype(Email.CONTENT_ITEM_TYPE);
    if (kind == null) {
      kind =
          addKind(
              new DataKind(
                  Email.CONTENT_ITEM_TYPE,
                  R.string.emailLabelsGroup,
                  android.R.drawable.sym_action_email,
                  15,
                  true));
      kind.actionHeader = new EmailActionInflater();
      kind.actionBody = new SimpleInflater(Email.DATA);
    }

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      kind.typeColumn = Email.TYPE;
      kind.typeList = Lists.newArrayList();
      kind.typeList.add(buildEmailType(Email.TYPE_HOME));
      kind.typeList.add(buildEmailType(Email.TYPE_WORK));
      kind.typeList.add(buildEmailType(Email.TYPE_OTHER));
      kind.typeList.add(buildEmailType(Email.TYPE_MOBILE));
      kind.typeList.add(
          buildEmailType(Email.TYPE_CUSTOM).setSecondary(true).setCustomColumn(Email.LABEL));

      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
    }

    return kind;
  }
  @Override
  protected DataKind inflateIm(Context context, int inflateLevel) {
    final DataKind kind = super.inflateIm(context, ContactsSource.LEVEL_MIMETYPES);

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      kind.typeOverallMax = 3;

      // NOTE: even though a traditional "type" exists, for editing
      // purposes we're using the protocol to pick labels

      kind.defaultValues = new ContentValues();
      kind.defaultValues.put(Im.TYPE, Im.TYPE_OTHER);

      kind.typeColumn = Im.PROTOCOL;
      kind.typeList = Lists.newArrayList();
      kind.typeList.add(buildImType(Im.PROTOCOL_AIM));
      kind.typeList.add(buildImType(Im.PROTOCOL_MSN));
      kind.typeList.add(buildImType(Im.PROTOCOL_YAHOO));
      kind.typeList.add(buildImType(Im.PROTOCOL_SKYPE));
      kind.typeList.add(buildImType(Im.PROTOCOL_QQ));
      kind.typeList.add(buildImType(Im.PROTOCOL_GOOGLE_TALK));
      kind.typeList.add(buildImType(Im.PROTOCOL_ICQ));
      kind.typeList.add(buildImType(Im.PROTOCOL_JABBER));
      kind.typeList.add(
          buildImType(Im.PROTOCOL_CUSTOM).setSecondary(true).setCustomColumn(Im.CUSTOM_PROTOCOL));

      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(new EditField(Im.DATA, R.string.imLabelsGroup, FLAGS_EMAIL));
    }

    return kind;
  }
  protected DataKind inflateOrganization(int inflateLevel) {
    DataKind kind = getKindForMimetype(Organization.CONTENT_ITEM_TYPE);
    if (kind == null) {
      kind =
          addKind(
              new DataKind(
                  Organization.CONTENT_ITEM_TYPE,
                  R.string.organizationLabelsGroup,
                  R.drawable.sym_action_organization,
                  30,
                  true));
      kind.actionHeader = new SimpleInflater(Organization.COMPANY);
      kind.actionBody = new SimpleInflater(Organization.TITLE);
    }

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      kind.typeColumn = Organization.TYPE;
      kind.typeList = Lists.newArrayList();
      kind.typeList.add(buildOrgType(Organization.TYPE_WORK));
      kind.typeList.add(buildOrgType(Organization.TYPE_OTHER));
      kind.typeList.add(
          buildOrgType(Organization.TYPE_CUSTOM)
              .setSecondary(true)
              .setCustomColumn(Organization.LABEL));

      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(
          new EditField(Organization.COMPANY, R.string.ghostData_company, FLAGS_GENERIC_NAME));
      kind.fieldList.add(
          new EditField(Organization.TITLE, R.string.ghostData_title, FLAGS_GENERIC_NAME));
    }

    return kind;
  }
  @Override
  protected DataKind inflatePhone(Context context, int inflateLevel) {
    final DataKind kind = super.inflatePhone(context, ContactsSource.LEVEL_MIMETYPES);
    Log.d(TAG, "in ExchangeSource inflatePhone, inflateLevel=" + inflateLevel);

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      kind.typeColumn = Phone.TYPE;
      kind.typeList = Lists.newArrayList();
      kind.typeList.add(buildPhoneType(Phone.TYPE_HOME).setSpecificMax(2));
      kind.typeList.add(buildPhoneType(Phone.TYPE_MOBILE).setSpecificMax(1));
      kind.typeList.add(buildPhoneType(Phone.TYPE_WORK).setSpecificMax(2));
      kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_WORK).setSecondary(true).setSpecificMax(1));
      kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_HOME).setSecondary(true).setSpecificMax(1));
      kind.typeList.add(buildPhoneType(Phone.TYPE_PAGER).setSecondary(true).setSpecificMax(1));
      kind.typeList.add(buildPhoneType(Phone.TYPE_CAR).setSecondary(true).setSpecificMax(1));
      kind.typeList.add(
          buildPhoneType(Phone.TYPE_COMPANY_MAIN).setSecondary(true).setSpecificMax(1));
      kind.typeList.add(buildPhoneType(Phone.TYPE_MMS).setSecondary(true).setSpecificMax(1));
      kind.typeList.add(buildPhoneType(Phone.TYPE_RADIO).setSecondary(true).setSpecificMax(1));
      kind.typeList.add(
          buildPhoneType(Phone.TYPE_CUSTOM).setSecondary(true).setCustomColumn(Phone.LABEL));
      // kind.typeList.add(buildPhoneType(Phone.TYPE_ASSISTANT).setSecondary(true)
      //        .setSpecificMax(1).setCustomColumn(Phone.LABEL));

      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
    }

    return kind;
  }
  protected DataKind inflatePhone(int inflateLevel) {
    DataKind kind = getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    if (kind == null) {
      kind =
          addKind(
              new DataKind(
                  Phone.CONTENT_ITEM_TYPE,
                  R.string.phoneLabelsGroup,
                  android.R.drawable.sym_action_call,
                  10,
                  true));
      kind.iconAltRes = R.drawable.sym_action_sms;
      kind.actionHeader = new PhoneActionInflater();
      kind.actionAltHeader = new PhoneActionAltInflater();
      kind.actionBody = new SimpleInflater(Phone.NUMBER);
    }

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      kind.typeColumn = Phone.TYPE;
      kind.typeList = Lists.newArrayList();
      kind.typeList.add(buildPhoneType(Phone.TYPE_HOME));
      kind.typeList.add(buildPhoneType(Phone.TYPE_MOBILE));
      kind.typeList.add(buildPhoneType(Phone.TYPE_WORK));
      kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_WORK).setSecondary(true));
      kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_HOME).setSecondary(true));
      kind.typeList.add(buildPhoneType(Phone.TYPE_PAGER).setSecondary(true));
      kind.typeList.add(buildPhoneType(Phone.TYPE_OTHER));
      kind.typeList.add(
          buildPhoneType(Phone.TYPE_CUSTOM).setSecondary(true).setCustomColumn(Phone.LABEL));
      kind.typeList.add(buildPhoneType(Phone.TYPE_CALLBACK).setSecondary(true));
      kind.typeList.add(buildPhoneType(Phone.TYPE_CAR).setSecondary(true));
      kind.typeList.add(buildPhoneType(Phone.TYPE_COMPANY_MAIN).setSecondary(true));
      kind.typeList.add(buildPhoneType(Phone.TYPE_ISDN).setSecondary(true));
      kind.typeList.add(buildPhoneType(Phone.TYPE_MAIN).setSecondary(true));
      kind.typeList.add(buildPhoneType(Phone.TYPE_OTHER_FAX).setSecondary(true));
      kind.typeList.add(buildPhoneType(Phone.TYPE_RADIO).setSecondary(true));
      kind.typeList.add(buildPhoneType(Phone.TYPE_TELEX).setSecondary(true));
      kind.typeList.add(buildPhoneType(Phone.TYPE_TTY_TDD).setSecondary(true));
      kind.typeList.add(buildPhoneType(Phone.TYPE_WORK_MOBILE).setSecondary(true));
      kind.typeList.add(buildPhoneType(Phone.TYPE_WORK_PAGER).setSecondary(true));
      kind.typeList.add(
          buildPhoneType(Phone.TYPE_ASSISTANT).setSecondary(true).setCustomColumn(Phone.LABEL));
      kind.typeList.add(buildPhoneType(Phone.TYPE_MMS).setSecondary(true));

      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
    }

    return kind;
  }
  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());
  }
  protected DataKind inflateStructuredPostal(int inflateLevel) {
    DataKind kind = getKindForMimetype(StructuredPostal.CONTENT_ITEM_TYPE);
    if (kind == null) {
      kind =
          addKind(
              new DataKind(
                  StructuredPostal.CONTENT_ITEM_TYPE,
                  R.string.postalLabelsGroup,
                  R.drawable.sym_action_map,
                  25,
                  true));
      kind.actionHeader = new PostalActionInflater();
      kind.actionBody = new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS);
    }

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      kind.typeColumn = StructuredPostal.TYPE;
      kind.typeList = Lists.newArrayList();
      kind.typeList.add(buildPostalType(StructuredPostal.TYPE_HOME));
      kind.typeList.add(buildPostalType(StructuredPostal.TYPE_WORK));
      kind.typeList.add(buildPostalType(StructuredPostal.TYPE_OTHER));
      kind.typeList.add(
          buildPostalType(StructuredPostal.TYPE_CUSTOM)
              .setSecondary(true)
              .setCustomColumn(StructuredPostal.LABEL));

      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(
          new EditField(StructuredPostal.STREET, R.string.postal_street, FLAGS_POSTAL));
      kind.fieldList.add(
          new EditField(StructuredPostal.POBOX, R.string.postal_pobox, FLAGS_POSTAL)
              .setOptional(true));
      kind.fieldList.add(
          new EditField(StructuredPostal.NEIGHBORHOOD, R.string.postal_neighborhood, FLAGS_POSTAL)
              .setOptional(true));
      kind.fieldList.add(new EditField(StructuredPostal.CITY, R.string.postal_city, FLAGS_POSTAL));
      kind.fieldList.add(
          new EditField(StructuredPostal.REGION, R.string.postal_region, FLAGS_POSTAL));
      kind.fieldList.add(
          new EditField(StructuredPostal.POSTCODE, R.string.postal_postcode, FLAGS_POSTAL));
      kind.fieldList.add(
          new EditField(StructuredPostal.COUNTRY, R.string.postal_country, FLAGS_POSTAL)
              .setOptional(true));
    }

    return kind;
  }
  /**
   * Build a list of {@link ContentProviderOperation} that will transform all the "before" {@link
   * Entity} states into the modified state which all {@link EntityDelta} objects represent. This
   * method specifically creates any {@link AggregationExceptions} rules needed to groups edits
   * together.
   */
  public ArrayList<ContentProviderOperation> buildDiff() {
    final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();

    final long rawContactId = this.findRawContactId();
    int firstInsertRow = -1;

    // First pass enforces versions remain consistent
    for (EntityDelta delta : this) {
      delta.buildAssert(diff);
    }

    final int assertMark = diff.size();
    int backRefs[] = new int[size()];

    int rawContactIndex = 0;

    // Second pass builds actual operations
    for (EntityDelta delta : this) {
      final int firstBatch = diff.size();
      backRefs[rawContactIndex++] = firstBatch;
      delta.buildDiff(diff);

      // Only create rules for inserts
      if (!delta.isContactInsert()) continue;

      // If we are going to split all contacts, there is no point in first combining them
      if (mSplitRawContacts) continue;

      if (rawContactId != -1) {
        // Has existing contact, so bind to it strongly
        final Builder builder = beginKeepTogether();
        builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId);
        builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, firstBatch);
        diff.add(builder.build());

      } else if (firstInsertRow == -1) {
        // First insert case, so record row
        firstInsertRow = firstBatch;

      } else {
        // Additional insert case, so point at first insert
        final Builder builder = beginKeepTogether();
        builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID1, firstInsertRow);
        builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, firstBatch);
        diff.add(builder.build());
      }
    }

    if (mSplitRawContacts) {
      buildSplitContactDiff(diff, backRefs);
    }

    // No real changes if only left with asserts
    if (diff.size() == assertMark) {
      diff.clear();
    }

    return diff;
  }
    @Override
    protected void inflate(Context context, int inflateLevel) {
      this.accountType = TEST_ACCOUNT_TYPE;
      this.setInflatedLevel(ContactsSource.LEVEL_CONSTRAINTS);

      // Phone allows maximum 2 home, 1 work, and unlimited other, with
      // constraint of 5 numbers maximum.
      DataKind kind = new DataKind(Phone.CONTENT_ITEM_TYPE, -1, -1, 10, true);

      kind.typeOverallMax = 5;
      kind.typeColumn = Phone.TYPE;
      kind.typeList = Lists.newArrayList();
      kind.typeList.add(new EditType(Phone.TYPE_HOME, -1).setSpecificMax(2));
      kind.typeList.add(new EditType(Phone.TYPE_WORK, -1).setSpecificMax(1));
      kind.typeList.add(new EditType(Phone.TYPE_FAX_WORK, -1).setSecondary(true));
      kind.typeList.add(new EditType(Phone.TYPE_OTHER, -1));

      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(new EditField(Phone.NUMBER, -1, -1));
      kind.fieldList.add(new EditField(Phone.LABEL, -1, -1));

      addKind(kind);

      // Email is unlimited
      kind = new DataKind(Email.CONTENT_ITEM_TYPE, -1, -1, 10, true);
      kind.typeOverallMax = -1;
      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(new EditField(Email.DATA, -1, -1));
      addKind(kind);

      // IM is only one
      kind = new DataKind(Im.CONTENT_ITEM_TYPE, -1, -1, 10, true);
      kind.typeOverallMax = 1;
      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(new EditField(Im.DATA, -1, -1));
      addKind(kind);

      // Organization is only one
      kind = new DataKind(Organization.CONTENT_ITEM_TYPE, -1, -1, 10, true);
      kind.typeOverallMax = 1;
      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(new EditField(Organization.COMPANY, -1, -1));
      kind.fieldList.add(new EditField(Organization.TITLE, -1, -1));
      addKind(kind);
    }
  /**
   * Deserialize a pattern.
   *
   * @param string The pattern serialized with {@link #patternToString}
   * @return The pattern.
   */
  public static List<LockPatternView.Cell> stringToPattern(String string) {
    List<LockPatternView.Cell> result = Lists.newArrayList();

    final byte[] bytes = string.getBytes();
    for (int i = 0; i < bytes.length; i++) {
      byte b = bytes[i];
      result.add(LockPatternView.Cell.of(b / 3, b % 3));
    }
    return result;
  }
  @Override
  protected DataKind inflateStructuredPostal(Context context, int inflateLevel) {
    final DataKind kind = super.inflateStructuredPostal(context, ContactsSource.LEVEL_MIMETYPES);

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      final boolean useJapaneseOrder =
          Locale.JAPANESE.getLanguage().equals(Locale.getDefault().getLanguage());
      kind.typeColumn = StructuredPostal.TYPE;
      kind.typeList = Lists.newArrayList();
      kind.typeList.add(buildPostalType(StructuredPostal.TYPE_WORK).setSpecificMax(1));
      kind.typeList.add(buildPostalType(StructuredPostal.TYPE_HOME).setSpecificMax(1));
      kind.typeList.add(buildPostalType(StructuredPostal.TYPE_OTHER).setSpecificMax(1));

      kind.fieldList = Lists.newArrayList();
      if (useJapaneseOrder) {
        kind.fieldList.add(
            new EditField(StructuredPostal.COUNTRY, R.string.postal_country, FLAGS_POSTAL)
                .setOptional(true));
        kind.fieldList.add(
            new EditField(StructuredPostal.POSTCODE, R.string.postal_postcode, FLAGS_POSTAL));
        kind.fieldList.add(
            new EditField(StructuredPostal.REGION, R.string.postal_region, FLAGS_POSTAL));
        kind.fieldList.add(
            new EditField(StructuredPostal.CITY, R.string.postal_city, FLAGS_POSTAL));
        kind.fieldList.add(
            new EditField(StructuredPostal.STREET, R.string.postal_street, FLAGS_POSTAL));
      } else {
        kind.fieldList.add(
            new EditField(StructuredPostal.STREET, R.string.postal_street, FLAGS_POSTAL));
        kind.fieldList.add(
            new EditField(StructuredPostal.CITY, R.string.postal_city, FLAGS_POSTAL));
        kind.fieldList.add(
            new EditField(StructuredPostal.REGION, R.string.postal_region, FLAGS_POSTAL));
        kind.fieldList.add(
            new EditField(StructuredPostal.POSTCODE, R.string.postal_postcode, FLAGS_POSTAL));
        kind.fieldList.add(
            new EditField(StructuredPostal.COUNTRY, R.string.postal_country, FLAGS_POSTAL)
                .setOptional(true));
      }
    }

    return kind;
  }
  /** Clean up orphan downloads, both in database and on disk. */
  public void cleanOrphans() {
    final ContentResolver resolver = getContentResolver();

    // Collect known files from database
    final HashSet<ConcreteFile> fromDb = Sets.newHashSet();
    final Cursor cursor =
        resolver.query(
            Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, OrphanQuery.PROJECTION, null, null, null);
    try {
      while (cursor.moveToNext()) {
        final String path = cursor.getString(OrphanQuery._DATA);
        if (TextUtils.isEmpty(path)) continue;

        final File file = new File(path);
        try {
          fromDb.add(new ConcreteFile(file));
        } catch (ErrnoException e) {
          // File probably no longer exists
          final String state = Environment.getExternalStorageState(file);
          if (Environment.MEDIA_UNKNOWN.equals(state) || Environment.MEDIA_MOUNTED.equals(state)) {
            // File appears to live on internal storage, or a
            // currently mounted device, so remove it from database.
            // This logic preserves files on external storage while
            // media is removed.
            final long id = cursor.getLong(OrphanQuery._ID);
            Slog.d(TAG, "Missing " + file + ", deleting " + id);
            resolver.delete(
                ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id),
                null,
                null);
          }
        }
      }
    } finally {
      IoUtils.closeQuietly(cursor);
    }

    // Collect known files from disk
    final int uid = android.os.Process.myUid();
    final ArrayList<ConcreteFile> fromDisk = Lists.newArrayList();
    fromDisk.addAll(listFilesRecursive(getCacheDir(), null, uid));
    fromDisk.addAll(listFilesRecursive(getFilesDir(), null, uid));
    fromDisk.addAll(listFilesRecursive(Environment.getDownloadCacheDirectory(), null, uid));

    Slog.d(TAG, "Found " + fromDb.size() + " files in database");
    Slog.d(TAG, "Found " + fromDisk.size() + " files on disk");

    // Delete files no longer referenced by database
    for (ConcreteFile file : fromDisk) {
      if (!fromDb.contains(file)) {
        Slog.d(TAG, "Missing db entry, deleting " + file.file);
        file.file.delete();
      }
    }
  }
  @Override
  protected DataKind inflateNote(Context context, int inflateLevel) {
    final DataKind kind = super.inflateNote(context, ContactsSource.LEVEL_MIMETYPES);

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE));
    }

    return kind;
  }
  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());
    }
  }
  protected DataKind inflateIm(int inflateLevel) {
    DataKind kind = getKindForMimetype(Im.CONTENT_ITEM_TYPE);
    if (kind == null) {
      kind =
          addKind(
              new DataKind(
                  Im.CONTENT_ITEM_TYPE,
                  R.string.imLabelsGroup,
                  android.R.drawable.sym_action_chat,
                  20,
                  true));
      kind.actionHeader = new ImActionInflater();
      kind.actionBody = new SimpleInflater(Im.DATA);
    }

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      // NOTE: even though a traditional "type" exists, for editing
      // purposes we're using the protocol to pick labels

      kind.defaultValues = new ContentValues();
      kind.defaultValues.put(Im.TYPE, Im.TYPE_OTHER);

      kind.typeColumn = Im.PROTOCOL;
      kind.typeList = Lists.newArrayList();
      kind.typeList.add(buildImType(Im.PROTOCOL_AIM));
      kind.typeList.add(buildImType(Im.PROTOCOL_MSN));
      kind.typeList.add(buildImType(Im.PROTOCOL_YAHOO));
      kind.typeList.add(buildImType(Im.PROTOCOL_SKYPE));
      kind.typeList.add(buildImType(Im.PROTOCOL_QQ));
      kind.typeList.add(buildImType(Im.PROTOCOL_GOOGLE_TALK));
      kind.typeList.add(buildImType(Im.PROTOCOL_ICQ));
      kind.typeList.add(buildImType(Im.PROTOCOL_JABBER));
      kind.typeList.add(
          buildImType(Im.PROTOCOL_CUSTOM).setSecondary(true).setCustomColumn(Im.CUSTOM_PROTOCOL));

      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(new EditField(Im.DATA, R.string.imLabelsGroup, FLAGS_EMAIL));
    }

    return kind;
  }
  /**
   * A batch is always created with at least one ShareInfo
   *
   * @param context, Context
   * @param info, BluetoothOppShareInfo
   */
  public BluetoothOppBatch(Context context, BluetoothOppShareInfo info) {
    BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    mContext = context;
    mShares = Lists.newArrayList();
    mTimestamp = info.mTimestamp;
    mDirection = info.mDirection;
    mDestination = adapter.getRemoteDevice(info.mDestination);
    mStatus = Constants.BATCH_STATUS_PENDING;
    mShares.add(info);

    if (V) Log.v(TAG, "New Batch created for info " + info.mId);
  }
  protected DataKind inflatePhoto(int inflateLevel) {
    DataKind kind = getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
    if (kind == null) {
      kind = addKind(new DataKind(Photo.CONTENT_ITEM_TYPE, -1, -1, -1, true));
    }

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
    }

    return kind;
  }
  @Override
  protected DataKind inflateEmail(Context context, int inflateLevel) {
    final DataKind kind = super.inflateEmail(context, ContactsSource.LEVEL_MIMETYPES);

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      kind.typeOverallMax = 3;

      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
    }

    return kind;
  }
  @Override
  protected DataKind inflateOrganization(Context context, int inflateLevel) {
    final DataKind kind = super.inflateOrganization(context, ContactsSource.LEVEL_MIMETYPES);

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      kind.isList = false;
      kind.typeColumn = Organization.TYPE;
      kind.typeList = Lists.newArrayList();
      kind.typeList.add(buildOrgType(Organization.TYPE_WORK).setSpecificMax(1));
      kind.typeList.add(buildOrgType(Organization.TYPE_OTHER).setSpecificMax(1));
      kind.typeList.add(
          buildOrgType(Organization.TYPE_CUSTOM).setSecondary(true).setSpecificMax(1));

      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(
          new EditField(Organization.COMPANY, R.string.ghostData_company, FLAGS_GENERIC_NAME));
      kind.fieldList.add(
          new EditField(Organization.TITLE, R.string.ghostData_title, FLAGS_GENERIC_NAME));
    }

    return kind;
  }
  @Override
  protected DataKind inflateWebsite(Context context, int inflateLevel) {
    final DataKind kind = super.inflateWebsite(context, ContactsSource.LEVEL_MIMETYPES);

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      kind.isList = false;

      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup, FLAGS_WEBSITE));
    }

    return kind;
  }
  @Override
  protected DataKind inflatePhoto(Context context, int inflateLevel) {
    final DataKind kind = super.inflatePhoto(context, ContactsSource.LEVEL_MIMETYPES);

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      kind.typeOverallMax = 1;

      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
    }

    return kind;
  }
 private static List<VpnProfile> loadVpnProfiles(KeyStore keyStore, int... excludeTypes) {
   final ArrayList<VpnProfile> result = Lists.newArrayList();
   final String[] keys = keyStore.saw(Credentials.VPN);
   if (keys != null) {
     for (String key : keys) {
       final VpnProfile profile = VpnProfile.decode(key, keyStore.get(Credentials.VPN + key));
       if (profile != null && !ArrayUtils.contains(excludeTypes, profile.type)) {
         result.add(profile);
       }
     }
   }
   return result;
 }
  @Override
  protected DataKind inflateNickname(Context context, int inflateLevel) {
    final DataKind kind = super.inflateNickname(context, ContactsSource.LEVEL_MIMETYPES);

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      kind.isList = false;

      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(
          new EditField(Nickname.NAME, R.string.nicknameLabelsGroup, FLAGS_PERSON_NAME));
    }

    return kind;
  }
    private void initProfiles(KeyStore keyStore, Resources res) {
      final String lockdownKey = getStringOrNull(keyStore, Credentials.LOCKDOWN_VPN);

      mProfiles = loadVpnProfiles(keyStore, VpnProfile.TYPE_PPTP);
      mTitles = Lists.newArrayList();
      mTitles.add(res.getText(R.string.vpn_lockdown_none));
      mCurrentIndex = 0;

      for (VpnProfile profile : mProfiles) {
        if (TextUtils.equals(profile.key, lockdownKey)) {
          mCurrentIndex = mTitles.size();
        }
        mTitles.add(profile.name);
      }
    }
  protected DataKind inflateStructuredName(int inflateLevel) {
    DataKind kind = getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE);
    if (kind == null) {
      kind =
          addKind(
              new DataKind(
                  StructuredName.CONTENT_ITEM_TYPE, R.string.nameLabelsGroup, -1, -1, true));
    }

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(
          new EditField(StructuredName.PREFIX, R.string.name_prefix, FLAGS_PERSON_NAME)
              .setOptional(true));
      kind.fieldList.add(
          new EditField(StructuredName.GIVEN_NAME, R.string.name_given, FLAGS_PERSON_NAME));
      kind.fieldList.add(
          new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle, FLAGS_PERSON_NAME)
              .setOptional(true));
      kind.fieldList.add(
          new EditField(StructuredName.FAMILY_NAME, R.string.name_family, FLAGS_PERSON_NAME));
      kind.fieldList.add(
          new EditField(StructuredName.SUFFIX, R.string.name_suffix, FLAGS_PERSON_NAME)
              .setOptional(true));
      kind.fieldList.add(
          new EditField(
                  StructuredName.PHONETIC_GIVEN_NAME, R.string.name_phonetic_given, FLAGS_PHONETIC)
              .setOptional(true));
      kind.fieldList.add(
          new EditField(
                  StructuredName.PHONETIC_MIDDLE_NAME,
                  R.string.name_phonetic_middle,
                  FLAGS_PHONETIC)
              .setOptional(true));
      kind.fieldList.add(
          new EditField(
                  StructuredName.PHONETIC_FAMILY_NAME,
                  R.string.name_phonetic_family,
                  FLAGS_PHONETIC)
              .setOptional(true));
    }

    return kind;
  }
  protected DataKind inflateNote(int inflateLevel) {
    DataKind kind = getKindForMimetype(Note.CONTENT_ITEM_TYPE);
    if (kind == null) {
      kind =
          addKind(
              new DataKind(
                  Note.CONTENT_ITEM_TYPE, R.string.label_notes, R.drawable.sym_note, 110, true));
      kind.secondary = true;
      kind.actionHeader = new SimpleInflater(R.string.label_notes);
      kind.actionBody = new SimpleInflater(Note.NOTE);
    }

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE));
    }

    return kind;
  }
  protected DataKind inflateWebsite(int inflateLevel) {
    DataKind kind = getKindForMimetype(Website.CONTENT_ITEM_TYPE);
    if (kind == null) {
      kind =
          addKind(
              new DataKind(Website.CONTENT_ITEM_TYPE, R.string.websiteLabelsGroup, -1, 120, true));
      kind.secondary = true;
      kind.actionHeader = new SimpleInflater(R.string.websiteLabelsGroup);
      kind.actionBody = new SimpleInflater(Website.URL);
    }

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      kind.defaultValues = new ContentValues();
      kind.defaultValues.put(Website.TYPE, Website.TYPE_OTHER);

      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup, FLAGS_WEBSITE));
    }

    return kind;
  }
  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());
    }
  }
  protected DataKind inflateNickname(int inflateLevel) {
    DataKind kind = getKindForMimetype(Nickname.CONTENT_ITEM_TYPE);
    if (kind == null) {
      kind =
          addKind(
              new DataKind(
                  Nickname.CONTENT_ITEM_TYPE, R.string.nicknameLabelsGroup, -1, 115, true));
      kind.secondary = true;
      kind.actionHeader = new SimpleInflater(R.string.nicknameLabelsGroup);
      kind.actionBody = new SimpleInflater(Nickname.NAME);
    }

    if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
      kind.defaultValues = new ContentValues();
      kind.defaultValues.put(Nickname.TYPE, Nickname.TYPE_DEFAULT);

      kind.fieldList = Lists.newArrayList();
      kind.fieldList.add(
          new EditField(Nickname.NAME, R.string.nicknameLabelsGroup, FLAGS_PERSON_NAME));
    }

    return kind;
  }
  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());
  }