/** * Funf: Open Sensing Framework * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. * Acknowledgments: Alan Gardner * Contact: nadav@media.mit.edu * * This file is part of Funf. * * Funf is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * Funf is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Funf. If not, see <http://www.gnu.org/licenses/>. */ package edu.mit.media.funf.probe.builtin; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.content.SharedPreferences; import android.database.Cursor; import android.os.Bundle; import android.provider.ContactsContract; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.Event; import android.provider.ContactsContract.CommonDataKinds.GroupMembership; import android.provider.ContactsContract.CommonDataKinds.Im; import android.provider.ContactsContract.CommonDataKinds.Nickname; import android.provider.ContactsContract.CommonDataKinds.Note; import android.provider.ContactsContract.CommonDataKinds.Organization; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.Photo; import android.provider.ContactsContract.CommonDataKinds.Relation; import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.Website; import android.util.Log; import edu.mit.media.funf.Utils; import edu.mit.media.funf.probe.ContentProviderProbe; import edu.mit.media.funf.probe.CursorCell; import edu.mit.media.funf.probe.CursorCell.HashedCell; import edu.mit.media.funf.probe.CursorCell.PhoneNumberCell; import edu.mit.media.funf.probe.builtin.ProbeKeys.ContactKeys; public class ContactProbe extends ContentProviderProbe implements ContactKeys { public static final Parameter FULL_PARAM = new Parameter("FULL", false, "FullScan", "If true, probe will return all contacts. If false, the scan will only return contacts that have changed since the most recent scan."); private static final String DATA_VERSIONS = "dataVersions"; private Map<Integer,Integer> dataIdToVersion; private boolean isFullScan; @Override public Parameter[] getAvailableParameters() { return new Parameter[] { new Parameter(Parameter.Builtin.PERIOD, 36000L), new Parameter(Parameter.Builtin.START, 0L), new Parameter(Parameter.Builtin.END, 0L), FULL_PARAM }; } @Override protected String getDisplayName() { return "Contacts Probe"; } @Override protected void onEnable() { super.onEnable(); SharedPreferences versionPrefs = getSharedPreferences(DATA_VERSIONS, MODE_PRIVATE); dataIdToVersion = new HashMap<Integer, Integer>(); for (Map.Entry<String, ?> versionEntry: versionPrefs.getAll().entrySet()) { dataIdToVersion.put(Integer.valueOf(versionEntry.getKey()), (Integer)versionEntry.getValue()); } isFullScan = false; } @Override protected void onDisable() { super.onDisable(); SharedPreferences.Editor versionPrefs = getSharedPreferences(DATA_VERSIONS, MODE_PRIVATE).edit(); versionPrefs.clear(); for (Map.Entry<Integer, Integer> idToVersion : dataIdToVersion.entrySet()) { versionPrefs.putInt(String.valueOf(idToVersion.getKey()), idToVersion.getValue()); } versionPrefs.commit(); } @Override protected Cursor getCursor(String[] projection) { return getContentResolver().query( ContactsContract.Data.CONTENT_URI, projection, null, //Data.MIMETYPE + " IN ('" + Email.CONTENT_ITEM_TYPE + "')", null,//new String[] {"('" + Utils.join(Arrays.asList(Email.MIMETYPE, Event.MIMETYPE), "','") +"')"}, Data.CONTACT_ID + " ASC"); } @Override protected String getDataName() { return CONTACT_DATA; } class ContactDataCell extends CursorCell<Object> { private Map<String,CursorCell<?>> cursorCells; ContactDataCell() { cursorCells = new HashMap<String, CursorCell<?>>(); // Email cursorCells.put(getKey(Email.CONTENT_ITEM_TYPE, Data.DATA1), hashedStringCell()); cursorCells.put(getKey(Email.CONTENT_ITEM_TYPE, Email.TYPE), intCell()); cursorCells.put(getKey(Email.CONTENT_ITEM_TYPE, Email.LABEL), stringCell()); cursorCells.put(getKey(Email.CONTENT_ITEM_TYPE, Email.DISPLAY_NAME), hashedStringCell()); // Event cursorCells.put(getKey(Event.CONTENT_ITEM_TYPE, Event.START_DATE), stringCell()); cursorCells.put(getKey(Event.CONTENT_ITEM_TYPE, Event.TYPE), intCell()); cursorCells.put(getKey(Event.CONTENT_ITEM_TYPE, Event.LABEL), hashedStringCell()); // Group Membership cursorCells.put(getKey(Event.CONTENT_ITEM_TYPE, GroupMembership.GROUP_ROW_ID), longCell()); // IM Address cursorCells.put(getKey(Im.CONTENT_ITEM_TYPE, Im.DATA), hashedStringCell()); cursorCells.put(getKey(Im.CONTENT_ITEM_TYPE, Im.TYPE), intCell()); cursorCells.put(getKey(Im.CONTENT_ITEM_TYPE, Im.LABEL), hashedStringCell()); cursorCells.put(getKey(Im.CONTENT_ITEM_TYPE, Im.PROTOCOL), stringCell()); cursorCells.put(getKey(Im.CONTENT_ITEM_TYPE, Im.CUSTOM_PROTOCOL), stringCell()); // Nickname cursorCells.put(getKey(Nickname.CONTENT_ITEM_TYPE, Nickname.NAME), hashedStringCell()); cursorCells.put(getKey(Nickname.CONTENT_ITEM_TYPE, Nickname.TYPE), intCell()); cursorCells.put(getKey(Nickname.CONTENT_ITEM_TYPE, Nickname.LABEL), stringCell()); // Note cursorCells.put(getKey(Note.CONTENT_ITEM_TYPE, Note.NOTE), hashedStringCell()); // TODO: do we need notes? // Organization cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Organization.COMPANY), hashedStringCell()); cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Organization.TYPE), intCell()); cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Organization.LABEL), hashedStringCell()); cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Organization.TITLE), hashedStringCell()); cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Organization.DEPARTMENT), hashedStringCell()); cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Organization.JOB_DESCRIPTION), hashedStringCell()); cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Organization.SYMBOL), hashedStringCell()); cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Organization.PHONETIC_NAME), hashedStringCell()); cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Organization.OFFICE_LOCATION), hashedStringCell()); cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Data.DATA10), stringCell()); // Phonetic_Name_Style // Phone // cursorCells.put(getKey(Phone.CONTENT_ITEM_TYPE, Phone.NUMBER), new PhoneNumberCell()); cursorCells.put(getKey(Phone.CONTENT_ITEM_TYPE, Phone.NUMBER), new HashedCell(ContactProbe.this, new PhoneNumberCell())); cursorCells.put(getKey(Phone.CONTENT_ITEM_TYPE, Phone.TYPE), intCell()); cursorCells.put(getKey(Phone.CONTENT_ITEM_TYPE, Phone.LABEL), stringCell()); // Photo (Skipped) cursorCells.put(getKey(Photo.CONTENT_ITEM_TYPE, Photo.PHOTO), new NullCell()); // Relation cursorCells.put(getKey(Relation.CONTENT_ITEM_TYPE, Relation.NAME), hashedStringCell()); cursorCells.put(getKey(Relation.CONTENT_ITEM_TYPE, Relation.TYPE), intCell()); cursorCells.put(getKey(Relation.CONTENT_ITEM_TYPE, Relation.LABEL), stringCell()); // SipAddress (API 9) Used defaults // StructuredName cursorCells.put(getKey(StructuredName.CONTENT_ITEM_TYPE, StructuredName.DISPLAY_NAME), hashedStringCell()); cursorCells.put(getKey(StructuredName.CONTENT_ITEM_TYPE, StructuredName.GIVEN_NAME), hashedStringCell()); cursorCells.put(getKey(StructuredName.CONTENT_ITEM_TYPE, StructuredName.FAMILY_NAME), hashedStringCell()); cursorCells.put(getKey(StructuredName.CONTENT_ITEM_TYPE, StructuredName.PREFIX), hashedStringCell()); cursorCells.put(getKey(StructuredName.CONTENT_ITEM_TYPE, StructuredName.MIDDLE_NAME), hashedStringCell()); cursorCells.put(getKey(StructuredName.CONTENT_ITEM_TYPE, StructuredName.SUFFIX), hashedStringCell()); cursorCells.put(getKey(StructuredName.CONTENT_ITEM_TYPE, StructuredName.PHONETIC_GIVEN_NAME), hashedStringCell()); cursorCells.put(getKey(StructuredName.CONTENT_ITEM_TYPE, StructuredName.PHONETIC_MIDDLE_NAME), hashedStringCell()); cursorCells.put(getKey(StructuredName.CONTENT_ITEM_TYPE, StructuredName.PHONETIC_FAMILY_NAME), hashedStringCell()); // Structured Postal cursorCells.put(getKey(StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.FORMATTED_ADDRESS), hashedStringCell()); cursorCells.put(getKey(StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.TYPE), intCell()); cursorCells.put(getKey(StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.LABEL), stringCell()); cursorCells.put(getKey(StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.STREET), hashedStringCell()); cursorCells.put(getKey(StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.POBOX), hashedStringCell()); cursorCells.put(getKey(StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.NEIGHBORHOOD), hashedStringCell()); cursorCells.put(getKey(StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.CITY), hashedStringCell()); cursorCells.put(getKey(StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.REGION), hashedStringCell()); cursorCells.put(getKey(StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.POSTCODE), hashedStringCell()); cursorCells.put(getKey(StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.COUNTRY), hashedStringCell()); // Website cursorCells.put(getKey(Website.CONTENT_ITEM_TYPE, Website.URL), hashedStringCell()); cursorCells.put(getKey(Website.CONTENT_ITEM_TYPE, Website.TYPE), intCell()); cursorCells.put(getKey(Website.CONTENT_ITEM_TYPE, Website.LABEL), stringCell()); } private String getKey(String mimeType, String columnName) { return mimeType + "__" + columnName; } @Override public Object getData(Cursor cursor, int columnIndex) { String mimeType = stringCell().getData(cursor,Data.MIMETYPE); String columnName = cursor.getColumnName(columnIndex); CursorCell<?> cursorCell = cursorCells.get(getKey(mimeType, columnName)); if (cursorCell == null) { cursorCell = anyCell(); // Default } return cursorCell.getData(cursor, columnIndex); } } private ContactDataCell contactDataCell; protected ContactDataCell contactDataCell() { if (contactDataCell == null) { contactDataCell = new ContactDataCell(); } return contactDataCell; } private Map<String, CursorCell<?>> dataProjectionMap; private Map<String, CursorCell<?>> getDataProjectionMap() { if (dataProjectionMap == null) { Map<String,CursorCell<?>> projectionKeyToType = new HashMap<String, CursorCell<?>>(); projectionKeyToType.put(Data._ID, intCell()); projectionKeyToType.put(Data.RAW_CONTACT_ID, longCell()); projectionKeyToType.put(Data.MIMETYPE, stringCell()); projectionKeyToType.put(Data.IS_PRIMARY, intCell()); projectionKeyToType.put(Data.IS_SUPER_PRIMARY, intCell()); projectionKeyToType.put(Data.DATA_VERSION, intCell()); projectionKeyToType.put(Data.DATA1, contactDataCell()); projectionKeyToType.put(Data.DATA2, contactDataCell()); projectionKeyToType.put(Data.DATA3, contactDataCell()); projectionKeyToType.put(Data.DATA4, contactDataCell()); projectionKeyToType.put(Data.DATA5, contactDataCell()); projectionKeyToType.put(Data.DATA6, contactDataCell()); projectionKeyToType.put(Data.DATA7, contactDataCell()); projectionKeyToType.put(Data.DATA8, contactDataCell()); projectionKeyToType.put(Data.DATA9, contactDataCell()); projectionKeyToType.put(Data.DATA10, contactDataCell()); projectionKeyToType.put(Data.DATA11, contactDataCell()); projectionKeyToType.put(Data.DATA12, contactDataCell()); projectionKeyToType.put(Data.DATA13, contactDataCell()); projectionKeyToType.put(Data.DATA14, contactDataCell()); projectionKeyToType.put(Data.DATA15, contactDataCell()); dataProjectionMap = projectionKeyToType; } return dataProjectionMap; } private Map<String, CursorCell<?>> contactProjectionMap; private Map<String, CursorCell<?>> getContactProjectionMap() { if (contactProjectionMap == null) { Map<String,CursorCell<?>> projectionKeyToType = new HashMap<String, CursorCell<?>>(); projectionKeyToType.put(Data.CONTACT_ID, longCell()); //projectionKeyToType.put(Data.AGGREGATION_MODE, intCell()); Doesn't exist for some reason //projectionKeyToType.put(Data.DELETED, intCell()); Doesn't exist for some reason projectionKeyToType.put(Data.LOOKUP_KEY, stringCell()); projectionKeyToType.put(Data.DISPLAY_NAME, hashedStringCell()); projectionKeyToType.put(Data.PHOTO_ID, longCell()); projectionKeyToType.put(Data.IN_VISIBLE_GROUP, intCell()); projectionKeyToType.put(Data.TIMES_CONTACTED, intCell()); projectionKeyToType.put(Data.LAST_TIME_CONTACTED, intCell()); projectionKeyToType.put(Data.STARRED, intCell()); projectionKeyToType.put(Data.CUSTOM_RINGTONE, hashedStringCell()); projectionKeyToType.put(Data.SEND_TO_VOICEMAIL, intCell()); contactProjectionMap = projectionKeyToType; } return contactProjectionMap; } @Override protected Map<String, CursorCell<?>> getProjectionMap() { Map<String,CursorCell<?>> projectionKeyToType = new HashMap<String, CursorCell<?>>(); projectionKeyToType.putAll(getDataProjectionMap()); projectionKeyToType.putAll(getContactProjectionMap()); return projectionKeyToType; } @Override protected void onRun(Bundle params) { isFullScan = params.getBoolean(FULL_PARAM.getName(), (Boolean)FULL_PARAM.getValue()); Log.i(TAG, "Full scan = " + isFullScan); super.onRun(params); } @Override protected Bundle parseDataBundle(Cursor cursor, String[] projection, Map<String, CursorCell<?>> projectionMap) { Bundle contactBundle = super.parseDataBundle(cursor, projection, getContactProjectionMap()); ArrayList<Bundle> dataBundles = new ArrayList<Bundle>(); long originalContactId = cursor.getLong(cursor.getColumnIndex(Data.CONTACT_ID)); long contactId = originalContactId; boolean hasNext = true; boolean hasChanged = isFullScan; do { Bundle dataBundle = super.parseDataBundle(cursor, projection, getDataProjectionMap()); dataBundles.add(dataBundle); int id = dataBundle.getInt(Data._ID); int version = dataBundle.getInt(Data.DATA_VERSION); Integer oldVersion = dataIdToVersion.get(id); if (oldVersion == null || !oldVersion.equals(version)) { hasChanged = true; dataIdToVersion.put(id, version); } hasNext = cursor.moveToNext(); if (hasNext) { contactId = cursor.getLong(cursor.getColumnIndex(Data.CONTACT_ID)); } } while(hasNext && originalContactId == contactId); if (hasNext) { cursor.moveToPrevious(); } contactBundle.putParcelableArrayList(CONTACT_DATA, dataBundles); return hasChanged ? contactBundle : null; } @Override public String[] getRequiredPermissions() { return new String[] { android.Manifest.permission.READ_CONTACTS }; } @Override protected long getTimestamp(List<Bundle> results) { return Utils.getTimestamp(); } @Override protected long getTimestamp(Bundle result) { return Utils.getTimestamp(); } @Override protected boolean sendEachRowSeparately() { return true; } }