/** * * 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.HashMap; import java.util.Map; import java.util.TreeMap; import android.database.Cursor; import android.provider.ContactsContract; 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.provider.ContactsContract.Data; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import edu.mit.media.funf.probe.Probe.ContinuableProbe; import edu.mit.media.funf.probe.Probe.DisplayName; import edu.mit.media.funf.probe.Probe.RequiredPermissions; import edu.mit.media.funf.probe.builtin.ProbeKeys.ContactKeys; @DisplayName("Contacts Probe") @RequiredPermissions(android.Manifest.permission.READ_CONTACTS) public class ContactProbe extends ContentProviderProbe implements ContactKeys, ContinuableProbe { private class VersionMap extends TreeMap<Integer,Integer> {private static final long serialVersionUID = 8835219249716647742L;} private VersionMap dataIdToVersion = new VersionMap(); @Override protected Cursor getCursor(String[] projection) { return getContext().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"); } private 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), sensitiveStringCell()); 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), sensitiveStringCell()); // 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), sensitiveStringCell()); // 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), sensitiveStringCell()); cursorCells.put(getKey(Im.CONTENT_ITEM_TYPE, Im.TYPE), intCell()); cursorCells.put(getKey(Im.CONTENT_ITEM_TYPE, Im.LABEL), sensitiveStringCell()); 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), sensitiveStringCell()); 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), sensitiveStringCell()); // TODO: do we need notes? // Organization cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Organization.COMPANY), sensitiveStringCell()); cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Organization.TYPE), intCell()); cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Organization.LABEL), sensitiveStringCell()); cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Organization.TITLE), sensitiveStringCell()); cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Organization.DEPARTMENT), sensitiveStringCell()); cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Organization.JOB_DESCRIPTION), sensitiveStringCell()); cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Organization.SYMBOL), sensitiveStringCell()); cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Organization.PHONETIC_NAME), sensitiveStringCell()); cursorCells.put(getKey(Organization.CONTENT_ITEM_TYPE, Organization.OFFICE_LOCATION), sensitiveStringCell()); 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.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), sensitiveStringCell()); 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), sensitiveStringCell()); cursorCells.put(getKey(StructuredName.CONTENT_ITEM_TYPE, StructuredName.GIVEN_NAME), sensitiveStringCell()); cursorCells.put(getKey(StructuredName.CONTENT_ITEM_TYPE, StructuredName.FAMILY_NAME), sensitiveStringCell()); cursorCells.put(getKey(StructuredName.CONTENT_ITEM_TYPE, StructuredName.PREFIX), sensitiveStringCell()); cursorCells.put(getKey(StructuredName.CONTENT_ITEM_TYPE, StructuredName.MIDDLE_NAME), sensitiveStringCell()); cursorCells.put(getKey(StructuredName.CONTENT_ITEM_TYPE, StructuredName.SUFFIX), sensitiveStringCell()); cursorCells.put(getKey(StructuredName.CONTENT_ITEM_TYPE, StructuredName.PHONETIC_GIVEN_NAME), sensitiveStringCell()); cursorCells.put(getKey(StructuredName.CONTENT_ITEM_TYPE, StructuredName.PHONETIC_MIDDLE_NAME), sensitiveStringCell()); cursorCells.put(getKey(StructuredName.CONTENT_ITEM_TYPE, StructuredName.PHONETIC_FAMILY_NAME), sensitiveStringCell()); // Structured Postal cursorCells.put(getKey(StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.FORMATTED_ADDRESS), sensitiveStringCell()); 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), sensitiveStringCell()); cursorCells.put(getKey(StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.POBOX), sensitiveStringCell()); cursorCells.put(getKey(StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.NEIGHBORHOOD), sensitiveStringCell()); cursorCells.put(getKey(StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.CITY), sensitiveStringCell()); cursorCells.put(getKey(StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.REGION), sensitiveStringCell()); cursorCells.put(getKey(StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.POSTCODE), sensitiveStringCell()); cursorCells.put(getKey(StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.COUNTRY), sensitiveStringCell()); // Website cursorCells.put(getKey(Website.CONTENT_ITEM_TYPE, Website.URL), sensitiveStringCell()); 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, sensitiveStringCell()); 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, sensitiveStringCell()); 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 JsonObject parseData(Cursor cursor, String[] projection, Map<String, CursorCell<?>> projectionMap) { JsonObject contact = super.parseData(cursor, projection, getContactProjectionMap()); JsonArray contactData = new JsonArray(); long originalContactId = cursor.getLong(cursor.getColumnIndex(Data.CONTACT_ID)); long contactId = originalContactId; boolean hasNext = true; boolean hasChanged = false; do { JsonObject contactDatum = super.parseData(cursor, projection, getDataProjectionMap()); contactData.add(contactDatum); int id = contactDatum.get(Data._ID).getAsInt(); int version = contactDatum.get(Data.DATA_VERSION).getAsInt(); 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(); } contact.add(CONTACT_DATA, contactData); return hasChanged ? contact : null; } @Override public void setCheckpoint(JsonElement checkpoint) { dataIdToVersion = (checkpoint == null) ? new VersionMap() : getGson().fromJson(checkpoint, VersionMap.class); } @Override public JsonElement getCheckpoint() { return getGson().toJsonTree(dataIdToVersion); } }