/* * Copyright 2012 The Stanford MobiSocial Laboratory * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package mobisocial.musubi.model.helpers; import gnu.trove.list.array.TLongArrayList; import gnu.trove.map.TLongIntMap; import gnu.trove.map.hash.TLongIntHashMap; import gnu.trove.procedure.TLongIntProcedure; import java.math.BigInteger; import java.util.Date; import java.util.LinkedList; import java.util.List; import mobisocial.crypto.IBHashedIdentity; import mobisocial.crypto.IBHashedIdentity.Authority; import mobisocial.crypto.IBIdentity; import mobisocial.musubi.App; import mobisocial.musubi.BJDNotImplementedException; import mobisocial.musubi.model.MIdentity; import mobisocial.musubi.model.MMyAccount; import mobisocial.musubi.provider.MusubiContentProvider; import mobisocial.musubi.provider.MusubiContentProvider.Provided; import mobisocial.musubi.service.MusubiService; import mobisocial.musubi.util.Util; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDoneException; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteStatement; import android.net.Uri; import android.provider.ContactsContract.Contacts; public class IdentitiesManager extends ManagerBase { public static final String PRE_INSTALL_IDENTITY_PRINCIPAL = "me@nowhere.com"; public static final String PRE_INSTALL_MUSUBI_PRINCIPAL = "musubi-feedback@lists.stanford.edu"; /** * Interval at which private keys are refreshed. */ public static final long KEY_REFRESH_SECONDS = 30 * 24 * 60 * 60; SQLiteStatement sqlInsertIdentity_; SQLiteStatement sqlUpdateIdentity_; SQLiteStatement sqlGetIdentityId_; SQLiteStatement sqlIncrementSequenceNumber_; SQLiteStatement sqlIsBlacklisted_; SQLiteStatement sqlIsMe_; SQLiteStatement sqlHasThumbnail_; SQLiteStatement sqlHasMusubiThumbnail_; SQLiteStatement sqlGetThumbnail_; SQLiteStatement sqlGetMusubiThumbnail_; SQLiteStatement sqlUpdateThumbnail_; SQLiteStatement sqlUpdateMusubiThumnail_; String sqlGetIdentityForId_; String sqlIdentityWithThumbnails_; /** * Mime type that can be used to launch a VIEW intent for an identity. */ public static final String MIME_TYPE = MusubiContentProvider.getType(Provided.IDENTITIES_ID); public IdentitiesManager(SQLiteOpenHelper databaseSource) { super(databaseSource); } public IdentitiesManager(SQLiteDatabase db) { super(db); } /* this is supposed to return a globally valid uri for sharing over wifi, etc */ public static Uri uriForMyIBHashedIdentity() { // return a long id instead; generate uri as MusubiContentProvider.uriForItem(Provided.IDENTITY, id) BJDNotImplementedException.except("TODO: discard me"); return null; } /* this extracts a hash from the globally valid uri */ public static IBHashedIdentity ibHashedIdentityForUri(Uri uri) { BJDNotImplementedException.except("TODO: discard me"); return null; } static final String[] STANDARD_FIELDS = new String[] { MIdentity.COL_ID, MIdentity.COL_TYPE, MIdentity.COL_PRINCIPAL_SHORT_HASH, MIdentity.COL_PRINCIPAL_HASH, MIdentity.COL_PRINCIPAL, MIdentity.COL_CLAIMED, MIdentity.COL_BLOCKED, MIdentity.COL_OWNED, MIdentity.COL_ANDROID_DATA_ID, MIdentity.COL_CONTACT_ID, MIdentity.COL_RECEIVED_PROFILE_VERSION, MIdentity.COL_SENT_PROFILE_VERSION, MIdentity.COL_NAME, MIdentity.COL_MUSUBI_NAME, MIdentity.COL_CREATED_AT, MIdentity.COL_UPDATED_AT, MIdentity.COL_NEXT_SEQUENCE_NUMBER, MIdentity.COL_HAS_SENT_EMAIL, MIdentity.COL_WHITELISTED }; static final String[] WITH_THUMBNAILS = joinArrays(STANDARD_FIELDS, new String[] { MIdentity.COL_THUMBNAIL, MIdentity.COL_MUSUBI_THUMBNAIL, }); static final int _id = 0; static final int type = 1; static final int principalShortHash = 2; static final int principalHash = 3; static final int principal = 4; static final int claimed = 5; static final int blocked = 6; static final int owned = 7; static final int androidDataId = 8; static final int contactId = 9; static final int receivedProfileVersion = 10; static final int sentProfileVersion = 11; static final int name = 12; static final int musubiName = 13; static final int createdAt = 14; static final int updatedAt = 15; static final int nextSequenceNumber = 16; static final int hasSentEmail = 17; static final int whitelisted = 18; static final int thumbnail = 19; static final int musubiThumbnail = 20; public List<MIdentity> getOwnedIdentities() { SQLiteDatabase db = initializeDatabase(); Cursor c = db.query(MIdentity.TABLE, STANDARD_FIELDS, MIdentity.COL_OWNED + "=1", null, null, null, null ); try { LinkedList<MIdentity> ids = new LinkedList<MIdentity>(); while(c.moveToNext()) { ids.add(fillInStandardFields(c)); } return ids; } finally { c.close(); } } public MIdentity getMyDefaultIdentity() { List<MIdentity> mine = getOwnedIdentities(); if (mine.size() == 0) return null; return mine.get(mine.size() > 1 ? 1 : 0); } /** * Gets a default identity "best-suited" for the given list of identities. * @param party the identities to use in matching. * @return */ public MIdentity getMyDefaultIdentity(MIdentity... party) { if (party.length == 0) { return getMyDefaultIdentity(); } for (MIdentity identity : party) { if (identity.owned_) { return identity; } } FeedManager fm = new FeedManager(initializeDatabase()); MyAccountManager am = new MyAccountManager(initializeDatabase()); MMyAccount[] accounts = am.getClaimedAccounts(null); if (accounts.length == 0) { return getMyDefaultIdentity(); } if (accounts.length == 1) { return getIdentityForId(accounts[0].identityId_); } TLongIntMap map = new TLongIntHashMap(); for (MMyAccount account : accounts) { // Sum up the number of identities in myIdentitys' account feeds. if (account.feedId_ == null) { continue; } int count; if (map.containsKey(account.identityId_)) { count = map.get(account.identityId_); } else { count = 0; } count += fm.countIdentitiesInFeed(account.feedId_, party); map.put(account.identityId_, count); } GetBestIdentity gbi = new GetBestIdentity(); map.forEachEntry(gbi); if (gbi.bestIdentity == -1) { return getMyDefaultIdentity(); } return getIdentityForId(gbi.bestIdentity); } class GetBestIdentity implements TLongIntProcedure { long bestIdentity = -1; int bestCount = -1; @Override public boolean execute(long id, int count) { if (count > bestCount) { bestIdentity = id; bestCount = count; } return true; } }; private MIdentity fillInStandardFields(Cursor c) { MIdentity id = new MIdentity(); id.id_ = c.getLong(_id); id.type_ = Authority.values()[(int) c.getLong(type)]; id.principalShortHash_ = c.getLong(principalShortHash); id.principalHash_ = c.getBlob(principalHash); id.principal_ = c.getString(principal); id.claimed_ = c.getLong(claimed) != 0; id.blocked_ = c.getLong(blocked) != 0; id.owned_ = c.getLong(owned) != 0; id.androidAggregatedContactId_ = c.isNull(androidDataId) ? null : c.getLong(androidDataId); id.contactId_ = c.isNull(contactId) ? null : c.getLong(contactId); id.receivedProfileVersion_ = c.getLong(receivedProfileVersion); id.sentProfileVersion_ = c.getLong(sentProfileVersion); id.name_ = c.getString(name); id.musubiName_ = c.getString(musubiName); id.createdAt_ = c.getLong(createdAt); id.updatedAt_ = c.getLong(updatedAt); id.nextSequenceNumber_ = c.getLong(nextSequenceNumber); id.hasSentEmail_ = c.getLong(hasSentEmail) != 0; id.whitelisted_ = c.getLong(whitelisted) != 0; return id; } public MIdentity getIdentityForIBHashedIdentity(IBHashedIdentity hid) { long id = getIdForIBHashedIdentity(hid); if (id <= 0) { return null; } return getIdentityForId(id); } public MIdentity getIdentityForId(long id) { if (sqlGetIdentityForId_ == null) { synchronized (this) { StringBuilder sql = new StringBuilder(100).append("SELECT "); for (String c : STANDARD_FIELDS) { sql.append(c).append(","); } sql.setLength(sql.length() - 1); sql.append(" FROM ").append(MIdentity.TABLE) .append(" WHERE ").append(MIdentity.COL_ID).append("=?"); sqlGetIdentityForId_ = sql.toString(); } } SQLiteDatabase db = initializeDatabase(); String[] selectionArgs = new String[] { String.valueOf(id) }; Cursor c = db.rawQuery(sqlGetIdentityForId_, selectionArgs); try { if (c.moveToNext()) { return fillInStandardFields(c); } return null; } finally { c.close(); } } public TLongArrayList getIdentityIdsForAggregateContactId(long id) { SQLiteDatabase db = initializeDatabase(); Cursor c = db.query(MIdentity.TABLE, new String[] { MIdentity.COL_ID }, MIdentity.COL_ANDROID_DATA_ID + "=?", new String[] { String.valueOf(id), }, null, null, null ); TLongArrayList ids = new TLongArrayList(4); try { while(c.moveToNext()) { ids.add(c.getLong(0)); } return ids; } finally { c.close(); } } public MIdentity[] getUpdatedIdentities(long lastUpdatedTime) { SQLiteDatabase db = initializeDatabase(); Cursor c = db.query(MIdentity.TABLE, STANDARD_FIELDS, MIdentity.COL_UPDATED_AT + ">?", new String[] { String.valueOf(lastUpdatedTime) }, null, null, MIdentity.COL_ANDROID_DATA_ID //don't index by this, index by time if at all ); MIdentity[] ids = new MIdentity[c.getCount()]; int i = 0; try { while(c.moveToNext()) { ids[i++] = fillInStandardFields(c); } } finally { c.close(); } return ids; } public MIdentity[] getIdentitiesForIds(long[] identityIds) { StringBuilder subselect = new StringBuilder(identityIds.length * 2 + 2); for (long id : identityIds) { subselect.append(",").append(id); } subselect.setCharAt(0, '('); subselect.append(')'); SQLiteDatabase db = initializeDatabase(); String table = MIdentity.TABLE; String selection = MIdentity.COL_ID + " in " + subselect; String[] selectionArgs = null; String having = null, groupBy = null, orderBy = null; Cursor c = db.query(table, STANDARD_FIELDS, selection, selectionArgs, groupBy, having, orderBy); MIdentity[] identities = new MIdentity[c.getCount()]; int i = 0; while(c.moveToNext()) { identities[i++] = fillInStandardFields(c); } try { return identities; } finally { c.close(); } } public MIdentity[] getIdentitiesForIdsGroupedByVisibleName(long[] identityIds) { StringBuilder sql = new StringBuilder(); sql.append("SELECT "); for (String field : STANDARD_FIELDS) { sql.append(field).append(","); } sql.setLength(sql.length() - 1); sql.append(" FROM ").append(MIdentity.TABLE).append(" WHERE ") .append(MIdentity.COL_ID).append(" IN ("); boolean first = true; for (long id : identityIds) { if (!first) sql.append(","); sql.append(id); first = false; } sql.append(") GROUP BY ").append(NAME_EXPR); sql.append(" ORDER BY ").append(NAME_EXPR); SQLiteDatabase db = initializeDatabase(); Cursor c = db.rawQuery(sql.toString(), null); MIdentity[] identities = new MIdentity[c.getCount()]; int i = 0; while(c.moveToNext()) { identities[i++] = fillInStandardFields(c); } try { return identities; } finally { sql.setLength(0); c.close(); } } static String[] joinArrays(String[] a, String[] b) { String[] c = new String[a.length + b.length]; int i = 0; for(int j = 0; j < a.length; ++j) c[i++] = a[j]; for(int j = 0; j < b.length; ++j) c[i++] = b[j]; return c; } public MIdentity getIdentityWithThumbnailsForId(long id) { SQLiteDatabase db = initializeDatabase(); if (sqlIdentityWithThumbnails_ == null) { StringBuilder sql = new StringBuilder(50); sql.append("SELECT "); for (String col : WITH_THUMBNAILS) { sql.append(col).append(","); } sql.setLength(sql.length() - 1); sql.append(" FROM ").append(MIdentity.TABLE) .append(" WHERE ").append(MIdentity.COL_ID).append("=?"); sqlIdentityWithThumbnails_ = sql.toString(); } Cursor c = db.rawQuery(sqlIdentityWithThumbnails_, new String[] { Long.toString(id) }); try { if (c.moveToNext()) { MIdentity ident = fillInStandardFields(c); ident.thumbnail_ = c.getBlob(thumbnail); ident.musubiThumbnail_ = c.getBlob(musubiThumbnail); return ident; } return null; } finally { c.close(); } } /** returns 0 if no id matches */ public long getIdForIBHashedIdentity(IBHashedIdentity hid) { SQLiteDatabase db = initializeDatabase(); if(sqlGetIdentityId_ == null) { synchronized(this) { if(sqlGetIdentityId_ == null) { sqlGetIdentityId_ = db.compileStatement( "SELECT " + MIdentity.COL_ID + " FROM " + MIdentity.TABLE + " WHERE " + MIdentity.COL_TYPE + "=? AND " + MIdentity.COL_PRINCIPAL_SHORT_HASH + "=? AND " + MIdentity.COL_PRINCIPAL_HASH + "=?" ); } } } synchronized (sqlGetIdentityId_) { sqlGetIdentityId_.bindLong(1, hid.authority_.ordinal()); sqlGetIdentityId_.bindLong(2, Util.shortHash(hid.hashed_)); sqlGetIdentityId_.bindBlob(3, hid.hashed_); try { return sqlGetIdentityId_.simpleQueryForLong(); } catch(SQLiteDoneException e) { return 0; } } } public long insertIdentity(MIdentity id) { assert(id.principalHash_ != null && Util.shortHash(id.principalHash_) == id.principalShortHash_); SQLiteDatabase db = initializeDatabase(); if(sqlInsertIdentity_ == null) { synchronized (this) { if(sqlInsertIdentity_ == null) { sqlInsertIdentity_ = db.compileStatement( "INSERT INTO " + MIdentity.TABLE + " (" + standardColumnsForInsert() + "," + MIdentity.COL_THUMBNAIL + "," + MIdentity.COL_MUSUBI_THUMBNAIL + ") " + "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" ); } } } synchronized (sqlInsertIdentity_) { id.createdAt_ = new Date().getTime()/1000; id.updatedAt_ = new Date().getTime()/1000; setAllButThumbnailIdentityColumns(sqlInsertIdentity_, id); if(id.thumbnail_ == null) { sqlInsertIdentity_.bindNull(thumbnail); } else { sqlInsertIdentity_.bindBlob(thumbnail, id.thumbnail_); } if(id.musubiThumbnail_ == null) { sqlInsertIdentity_.bindNull(musubiThumbnail); } else { sqlInsertIdentity_.bindBlob(musubiThumbnail, id.musubiThumbnail_); } id.id_ = sqlInsertIdentity_.executeInsert(); return id.id_; } } private static String standardColumnsForInsert() { String v = STANDARD_FIELDS[1]; for(int i = 2; i < STANDARD_FIELDS.length; ++i) { v += "," + STANDARD_FIELDS[i]; } return v; } public void updateIdentity(MIdentity id) { assert(id.id_ != 0); assert(id.principalHash_ != null && Util.shortHash(id.principalHash_) == id.principalShortHash_); SQLiteDatabase db = initializeDatabase(); if(sqlUpdateIdentity_ == null) { synchronized (this) { if(sqlUpdateIdentity_ == null) { sqlUpdateIdentity_ = db.compileStatement( "UPDATE " + MIdentity.TABLE + " SET " + MIdentity.COL_TYPE + "=?," + MIdentity.COL_PRINCIPAL_SHORT_HASH + "=?," + MIdentity.COL_PRINCIPAL_HASH + "=?," + MIdentity.COL_PRINCIPAL + "=?," + MIdentity.COL_CLAIMED + "=?," + MIdentity.COL_BLOCKED + "=?," + MIdentity.COL_OWNED + "=?," + MIdentity.COL_ANDROID_DATA_ID + "=?," + MIdentity.COL_CONTACT_ID + "=?," + MIdentity.COL_RECEIVED_PROFILE_VERSION + "=?," + MIdentity.COL_SENT_PROFILE_VERSION + "=?," + MIdentity.COL_NAME + "=?, " + MIdentity.COL_MUSUBI_NAME + "=?, " + MIdentity.COL_CREATED_AT + "=?," + MIdentity.COL_UPDATED_AT + "=?," + MIdentity.COL_NEXT_SEQUENCE_NUMBER + "=?," + MIdentity.COL_HAS_SENT_EMAIL + "=?," + MIdentity.COL_WHITELISTED + "=?" + " WHERE " + MIdentity.COL_ID + "=?" ); } } } id.updatedAt_ = new Date().getTime()/1000; synchronized (sqlUpdateIdentity_) { setAllButThumbnailIdentityColumns(sqlUpdateIdentity_, id); sqlUpdateIdentity_.bindLong(whitelisted+1, id.id_); sqlUpdateIdentity_.execute(); } } private static void setAllButThumbnailIdentityColumns(SQLiteStatement statement, MIdentity id) { statement.bindLong(type, id.type_.ordinal()); statement.bindLong(principalShortHash, id.principalShortHash_); statement.bindBlob(principalHash, id.principalHash_); if(id.principal_ == null) { statement.bindNull(principal); } else { statement.bindString(principal, id.principal_); } statement.bindLong(claimed, id.claimed_ ? 1 : 0); statement.bindLong(blocked, id.blocked_ ? 1 : 0); statement.bindLong(owned, id.owned_ ? 1 : 0); if(id.androidAggregatedContactId_ == null) { statement.bindNull(androidDataId); } else { statement.bindLong(androidDataId, id.androidAggregatedContactId_); } if(id.contactId_ == null) { statement.bindNull(contactId); } else { statement.bindLong(contactId, id.contactId_); } statement.bindLong(receivedProfileVersion, id.receivedProfileVersion_); statement.bindLong(sentProfileVersion, id.sentProfileVersion_); if(id.name_ == null) { statement.bindNull(name); } else { statement.bindString(name, id.name_); } if(id.musubiName_ == null) { statement.bindNull(musubiName); } else { statement.bindString(musubiName, id.musubiName_); } if(id.createdAt_ != 0) { statement.bindLong(createdAt, id.createdAt_); } if(id.updatedAt_ != 0) { statement.bindLong(updatedAt, id.updatedAt_); } statement.bindLong(nextSequenceNumber, id.nextSequenceNumber_); statement.bindLong(hasSentEmail, id.hasSentEmail_ ? 1 : 0); statement.bindLong(whitelisted, id.whitelisted_ ? 1 : 0); } /** MIdentities row must have populated id and sequence number */ public void incrementSequenceNumber(MIdentity to) { SQLiteDatabase db = initializeDatabase(); if(sqlIncrementSequenceNumber_ == null) { synchronized(this) { if(sqlIncrementSequenceNumber_ == null) { sqlIncrementSequenceNumber_ = db.compileStatement( "UPDATE " + MIdentity.TABLE + " SET " + MIdentity.COL_NEXT_SEQUENCE_NUMBER + "=" + MIdentity.COL_NEXT_SEQUENCE_NUMBER + "+1" + " WHERE " + MIdentity.COL_ID + "=? " ); } } } synchronized (sqlIncrementSequenceNumber_) { sqlIncrementSequenceNumber_.bindLong(1, to.id_); sqlIncrementSequenceNumber_.execute(); } //if they had a valid sequence number before, they still will... otherwise undefined ++to.nextSequenceNumber_; } public boolean isBlacklisted(MIdentity from) { SQLiteDatabase db = initializeDatabase(); if(sqlIsBlacklisted_ == null) { synchronized(this) { if(sqlIsBlacklisted_ == null) { sqlIsBlacklisted_ = db.compileStatement( "SELECT COUNT(*) FROM " + MIdentity.TABLE + " WHERE " + MIdentity.COL_ID + "=? AND " + MIdentity.COL_BLOCKED + "=1" ); } } } synchronized (sqlIsBlacklisted_) { sqlIsBlacklisted_.bindLong(1, from.id_); return sqlIsBlacklisted_.simpleQueryForLong() > 0; } } public boolean isMe(IBHashedIdentity ibHashedIdentity) { SQLiteDatabase db = initializeDatabase(); if(sqlIsMe_ == null) { synchronized(this) { if(sqlIsMe_ == null) { sqlIsMe_ = db.compileStatement( "SELECT COUNT(*) FROM " + MIdentity.TABLE + " WHERE " + MIdentity.COL_TYPE + "=? AND " + MIdentity.COL_PRINCIPAL_SHORT_HASH + "=? AND " + MIdentity.COL_PRINCIPAL_HASH + "=? AND " + MIdentity.COL_OWNED + "=1" ); } } } synchronized (sqlIsMe_) { sqlIsMe_.bindLong(1, ibHashedIdentity.authority_.ordinal()); sqlIsMe_.bindLong(2, Util.shortHash(ibHashedIdentity.hashed_)); sqlIsMe_.bindBlob(3, ibHashedIdentity.hashed_); return sqlIsMe_.simpleQueryForLong() > 0; } } public boolean hasThumbnail(MIdentity id) { SQLiteDatabase db = initializeDatabase(); if(sqlHasThumbnail_ == null) { synchronized(this) { if(sqlHasThumbnail_ == null) { sqlHasThumbnail_ = db.compileStatement( "SELECT " + MIdentity.COL_THUMBNAIL + " IS NOT NULL FROM " + MIdentity.TABLE + " WHERE " + MIdentity.COL_ID + "=?" ); } } } synchronized (sqlHasThumbnail_) { sqlHasThumbnail_.bindLong(1, id.id_); try { return sqlHasThumbnail_.simpleQueryForLong() != 0; } catch(SQLiteDoneException e) { return false; } } } public boolean hasMusubiThumbnail(MIdentity id) { SQLiteDatabase db = initializeDatabase(); if(sqlHasMusubiThumbnail_ == null) { synchronized(this) { if(sqlHasMusubiThumbnail_ == null) { sqlHasMusubiThumbnail_ = db.compileStatement( "SELECT " + MIdentity.COL_MUSUBI_THUMBNAIL + " IS NOT NULL FROM " + MIdentity.TABLE + " WHERE " + MIdentity.COL_ID + "=?" ); } } } synchronized (sqlHasMusubiThumbnail_) { sqlHasMusubiThumbnail_.bindLong(1, id.id_); try { return sqlHasMusubiThumbnail_.simpleQueryForLong() != 0; } catch(SQLiteDoneException e) { return false; } } } public byte[] getThumbnail(MIdentity id) { SQLiteDatabase db = initializeDatabase(); Cursor c = db.rawQuery("SELECT " + MIdentity.COL_THUMBNAIL + " FROM " + MIdentity.TABLE + " WHERE " + MIdentity.COL_ID + "=?", new String[] {String.valueOf(id.id_)}); try { while(c.moveToNext()) { id.thumbnail_ = c.getBlob(0); return id.thumbnail_; } return null; } finally { c.close(); } } public byte[] getMusubiThumbnail(MIdentity id) { SQLiteDatabase db = initializeDatabase(); Cursor c = db.rawQuery("SELECT " + MIdentity.COL_MUSUBI_THUMBNAIL + " FROM " + MIdentity.TABLE + " WHERE " + MIdentity.COL_ID + "=?", new String[] {String.valueOf(id.id_)}); try { while(c.moveToNext()) { id.musubiThumbnail_ = c.getBlob(0); return id.musubiThumbnail_; } return null; } finally { c.close(); } } public void updateThumbnail(MIdentity id) { SQLiteDatabase db = initializeDatabase(); if(sqlUpdateThumbnail_ == null) { synchronized(this) { if(sqlUpdateThumbnail_ == null) { sqlUpdateThumbnail_ = db.compileStatement( "UPDATE " + MIdentity.TABLE + " SET " + MIdentity.COL_THUMBNAIL + "=? WHERE " + MIdentity.COL_ID + "=?" ); } } } synchronized (sqlUpdateThumbnail_) { if(id.thumbnail_ == null) { sqlUpdateThumbnail_.bindNull(1); } else { sqlUpdateThumbnail_.bindBlob(1, id.thumbnail_); } sqlUpdateThumbnail_.bindLong(2, id.id_); sqlUpdateThumbnail_.execute(); } } public void updateMusubiThumbnail(MIdentity id) { SQLiteDatabase db = initializeDatabase(); if(sqlUpdateMusubiThumnail_ == null) { synchronized(this) { if(sqlUpdateMusubiThumnail_ == null) { sqlUpdateMusubiThumnail_ = db.compileStatement( "UPDATE " + MIdentity.TABLE + " SET " + MIdentity.COL_MUSUBI_THUMBNAIL + "=? WHERE " + MIdentity.COL_ID + "=?" ); } } } synchronized (sqlUpdateMusubiThumnail_) { if(id.musubiThumbnail_ == null) { sqlUpdateMusubiThumnail_.bindNull(1); } else { sqlUpdateMusubiThumnail_.bindBlob(1, id.musubiThumbnail_); } sqlUpdateMusubiThumnail_.bindLong(2, id.id_); sqlUpdateMusubiThumnail_.execute(); } } public static long computeTemporalFrameFromHash(byte[] hashed) { long offset = new BigInteger(hashed) .mod(BigInteger.valueOf(KEY_REFRESH_SECONDS)).longValue(); long interval = (new Date().getTime() / 1000 - offset) / KEY_REFRESH_SECONDS; return (interval * KEY_REFRESH_SECONDS) + offset; } public static long computeTemporalFrameFromPrincipal(String principal) { return computeTemporalFrameFromHash(IBIdentity.digestPrincipal(principal)); } public static IBHashedIdentity toIBHashedIdentity(MIdentity i, long temporalFrame) { return new IBHashedIdentity(i.type_, i.principalHash_, temporalFrame); } public static IBIdentity toIBIdentity(MIdentity i, long temporalFrame) { if(i.principal_ == null) return null; return new IBIdentity(i.type_, i.principal_, temporalFrame); } static final String NAME_EXPR = "COALESCE(" + MIdentity.COL_MUSUBI_NAME + "," + MIdentity.COL_NAME + "," + MIdentity.COL_PRINCIPAL + "," + MIdentity.COL_PRINCIPAL_SHORT_HASH + ")"; /** * Returns a cursor of whitelisted identities */ public Cursor getWhiteListIdentitiesCursor() { SQLiteDatabase db = initializeDatabase(); String table = MIdentity.TABLE; String[] columns = new String[] { MIdentity.COL_ID, NAME_EXPR + " AS displayname" }; String selection = MIdentity.COL_WHITELISTED + "=1 AND " + MIdentity.COL_OWNED + " = 0"; String[] selectionArgs = null; String groupBy = null, having = null; String orderBy = MIdentity.COL_CLAIMED + " DESC, displayname COLLATE NOCASE ASC"; Cursor c = db.query( table, columns, selection, selectionArgs, groupBy, having, orderBy); return c; } /** * Returns a cursor of graylisted identities */ public Cursor getGrayListIdentitiesCursor() { //TODO: in developer mode, show don't supress unknown users. SQLiteDatabase db = initializeDatabase(); int localType = IBHashedIdentity.Authority.Local.ordinal(); String table = MIdentity.TABLE; String[] columns = new String[] { MIdentity.COL_ID, NAME_EXPR + " AS displayname" }; String selection = MIdentity.COL_WHITELISTED + "=0 AND " + MIdentity.COL_OWNED + " = 0 AND " + MIdentity.COL_TYPE + " <> " + localType + " AND " + "(" + MIdentity.TABLE + "." + MIdentity.COL_MUSUBI_NAME + " IS NOT NULL OR " + MIdentity.TABLE + "." + MIdentity.COL_NAME + " IS NOT NULL OR " + MIdentity.TABLE + "." + MIdentity.COL_THUMBNAIL + " IS NOT NULL OR " + MIdentity.TABLE + "." + MIdentity.COL_MUSUBI_THUMBNAIL + " IS NOT NULL OR " + MIdentity.TABLE + "." + MIdentity.COL_PRINCIPAL + " IS NOT NULL" + ")"; String[] selectionArgs = null; String groupBy = null, having = null; String orderBy = MIdentity.COL_BLOCKED + " ASC," + MIdentity.COL_CLAIMED + " DESC, displayname COLLATE NOCASE ASC"; Cursor c = db.query( table, columns, selection, selectionArgs, groupBy, having, orderBy); return c; } /** * Returns a count of graylisted identities that need to be dealt with by the user */ public int getPendingGraylistCount() { //TODO: in developer mode, show don't supress unknown users. SQLiteDatabase db = initializeDatabase(); String table = MIdentity.TABLE; String[] columns = new String[] { "COUNT(" + MIdentity.COL_ID + ")" }; String selection = MIdentity.COL_WHITELISTED + "=0 AND " + MIdentity.COL_OWNED + " = 0" + " AND " + MIdentity.COL_BLOCKED +"=0" + " AND " + MIdentity.COL_TYPE +"<>" + Authority.Local.ordinal() + " AND " + "(" + MIdentity.TABLE + "." + MIdentity.COL_MUSUBI_NAME + " IS NOT NULL OR " + MIdentity.TABLE + "." + MIdentity.COL_NAME + " IS NOT NULL OR " + MIdentity.TABLE + "." + MIdentity.COL_THUMBNAIL + " IS NOT NULL OR " + MIdentity.TABLE + "." + MIdentity.COL_MUSUBI_THUMBNAIL + " IS NOT NULL OR " + MIdentity.TABLE + "." + MIdentity.COL_PRINCIPAL + " IS NOT NULL" + ")"; String[] selectionArgs = null; String groupBy = null, having = null; String orderBy = null; Cursor c = db.query( table, columns, selection, selectionArgs, groupBy, having, orderBy); try { while(c.moveToNext()) { return c.getInt(0); } return 0; } finally { c.close(); } } /** * Returns a subquery of all of this device's owned identities, in the form: * (2,3,9) */ public String getOwnedIdentitiesSubquery() { SQLiteDatabase db = initializeDatabase(); String table = MIdentity.TABLE; String[] columns = new String[] { MIdentity.COL_ID }; String selection = MIdentity.COL_OWNED + " = 1"; String[] selectionArgs = null; String groupBy = null, having = null, orderBy = null; Cursor c = db.query( table, columns, selection, selectionArgs, groupBy, having, orderBy); if (!c.moveToFirst()) { try { return "(-1)"; } finally { c.close(); } } StringBuilder b = new StringBuilder("("); do { b.append(c.getLong(0)).append(","); } while (c.moveToNext()); b.setCharAt(b.length() - 1, ')'); try { return b.toString(); } finally { c.close(); } } public IBIdentity getIBIdentityForIBHashedIdentity(IBHashedIdentity hid) { MIdentity ident = getIdentityForIBHashedIdentity(hid); if(ident == null || ident.principal_ == null) return null; return new IBIdentity(hid.authority_, ident.principal_, hid.temporalFrame_); } /** * A stub identity Musubi ships with. */ public static IBIdentity getPreInstallIdentity() { return new IBIdentity(IBHashedIdentity.Authority.Local, IdentitiesManager.PRE_INSTALL_IDENTITY_PRINCIPAL, 0); } /** * A stub identity Musubi ships with. */ public static IBIdentity getPreInstallMusubiIdentity() { return new IBIdentity(IBHashedIdentity.Authority.Local, IdentitiesManager.PRE_INSTALL_MUSUBI_PRINCIPAL, 0); } /** * Sets the thumbnail for all of this user's owned identities. * @See ProfilePushProcessor * @See MusubiService */ public void updateMyProfileThumbnail(Context context, byte[] data, boolean notify) { Date now = new Date(); SQLiteDatabase db = initializeDatabase(); db.beginTransaction(); try { for (MIdentity me : getOwnedIdentities()) { me.musubiThumbnail_ = data; me.receivedProfileVersion_ = now.getTime(); updateIdentity(me); updateMusubiThumbnail(me); App.getContactCache(context).invalidate(me.id_); } db.setTransactionSuccessful(); } finally { db.endTransaction(); } if (notify) { context.getContentResolver().notifyChange(MusubiService.MY_PROFILE_UPDATED, null); context.getContentResolver().notifyChange(MusubiService.PRIMARY_CONTENT_CHANGED, null); } } /** * Sets the name for all of this user's owned identities. * @See ProfilePushProcessor * @See MusubiService */ public void updateMyProfileName(Context context, String name, boolean notify) { Date now = new Date(); SQLiteDatabase db = initializeDatabase(); db.beginTransaction(); try { for (MIdentity me : getOwnedIdentities()) { me.musubiName_ = name; me.receivedProfileVersion_ = now.getTime(); updateIdentity(me); } db.setTransactionSuccessful(); } finally { db.endTransaction(); } if (notify) { context.getContentResolver().notifyChange(MusubiService.MY_PROFILE_UPDATED, null); context.getContentResolver().notifyChange(MusubiService.PRIMARY_CONTENT_CHANGED, null); } } public boolean isWhitelisted(MIdentity owner) { return (owner.owned_ || owner.androidAggregatedContactId_ != null); } public MIdentity ensureClaimedIdentity(IBHashedIdentity hid) { MIdentity id = getIdentityForIBHashedIdentity(hid); if(id != null) { if(!id.claimed_) { id.claimed_ = true; updateIdentity(id); } return id; } id = new MIdentity(); id.claimed_ = true; id.principalHash_ = hid.hashed_; id.principalShortHash_ = Util.shortHash(hid.hashed_); id.type_ = hid.authority_; id.hasSentEmail_ = true; insertIdentity(id); return id; } public static String androidLookupKeyForIdentitiy(Context context, MIdentity person) { if (person.androidAggregatedContactId_ == null) { return null; } Uri uri = Contacts.CONTENT_URI; String[] projection = new String[] { Contacts.LOOKUP_KEY }; String selection = Contacts._ID + " = ?"; String[] selectionArgs = new String[] { Long.toString(person.androidAggregatedContactId_) }; String sortOrder = null; Cursor c = context.getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder); try { if (c.moveToFirst()) { return c.getString(0); } else { return null; } } finally { c.close(); } } public boolean hasConnectedAccounts() { List<MIdentity> owned = getOwnedIdentities(); // we create a dummy account for the wizard. return owned.size() > 1; } @Override public synchronized void close() { if (sqlInsertIdentity_ != null) { sqlInsertIdentity_.close(); sqlInsertIdentity_ = null; } if (sqlUpdateIdentity_ != null) { sqlUpdateIdentity_.close(); sqlUpdateIdentity_ = null; } if (sqlGetIdentityId_ != null) { sqlGetIdentityId_.close(); sqlGetIdentityId_ = null; } if (sqlIncrementSequenceNumber_ != null) { sqlIncrementSequenceNumber_.close(); sqlIncrementSequenceNumber_ = null; } if (sqlIsBlacklisted_ != null) { sqlIsBlacklisted_.close(); sqlIsBlacklisted_ = null; } if (sqlIsMe_ != null) { sqlIsMe_.close(); sqlIsMe_ = null; } if (sqlHasThumbnail_ != null) { sqlHasThumbnail_.close(); sqlHasThumbnail_ = null; } if (sqlHasMusubiThumbnail_ != null) { sqlHasMusubiThumbnail_.close(); sqlHasMusubiThumbnail_ = null; } if (sqlGetThumbnail_ != null) { sqlGetThumbnail_.close(); sqlGetThumbnail_ = null; } if (sqlGetMusubiThumbnail_ != null) { sqlGetMusubiThumbnail_.close(); sqlGetMusubiThumbnail_ = null; } if (sqlUpdateThumbnail_ != null) { sqlUpdateThumbnail_.close(); sqlUpdateThumbnail_ = null; } if (sqlUpdateMusubiThumnail_ != null) { sqlUpdateMusubiThumnail_.close(); sqlUpdateMusubiThumnail_ = null; } } }