/* * 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.ui.util; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Random; import mobisocial.crypto.IBHashedIdentity.Authority; import mobisocial.crypto.IBIdentity; import mobisocial.musubi.App; import mobisocial.musubi.R; import mobisocial.musubi.model.MFeed; import mobisocial.musubi.model.MIdentity; import mobisocial.musubi.model.MMyAccount; import mobisocial.musubi.model.helpers.FeedManager; import mobisocial.musubi.model.helpers.IdentitiesManager; import mobisocial.musubi.model.helpers.MyAccountManager; import mobisocial.musubi.provider.MusubiContentProvider; import mobisocial.musubi.provider.MusubiContentProvider.Provided; import mobisocial.musubi.service.MusubiService; import mobisocial.musubi.ui.widget.MultiIdentitySelector; import mobisocial.musubi.util.IdentityCache; import mobisocial.musubi.util.IdentityCache.CachedIdentity; import mobisocial.musubi.util.Util; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; import android.util.Log; public class UiUtil { /** * Do some pretty printing on feed names? * If it's a fixed feed and the name is currently blank, give it a default name */ public static String getFeedNameFromMembersList(FeedManager feedManager, MFeed feed) { if(feed.name_ != null && feed.name_.length() > 0) { return feed.name_; } MIdentity[] members = feedManager.getFeedMembersGroupedByVisibleName(feed); StringBuilder name = new StringBuilder(80); for (int i = 0; i < members.length; i++) { String one_name = internalSafeNameForIdentity(members[i]); if(one_name == null) continue; if(name.length() > 0) { name.append(", "); } name.append(one_name); } if(name.length() > 0) return name.toString(); return "Top Secret"; } public static String internalSafeNameForIdentity(MIdentity ident) { if (ident == null) { return null; } if(ident.musubiName_ != null) { return ident.musubiName_; } else if(ident.name_ != null) { return ident.name_; } else if(ident.principal_ != null) { return safePrincipalForIdentity(ident); } else { return null; } } public static String safeNameForIdentity(MIdentity ident) { String name = internalSafeNameForIdentity(ident); if(name != null) { return name; } return "Unknown"; } public static String safePrincipalForIdentity(MIdentity ident) { //face book identities should pretty much always have an associated name //for us to use. We consider the users name to be their identity at facebook //for the purposes of display. if(ident.type_ == Authority.Facebook && ident.name_ != null) { return "Facebook: " + ident.name_; } if(ident.principal_ != null) { if(ident.type_ == Authority.Email) return ident.principal_; if(ident.type_ == Authority.Facebook) return "Facebook #" + ident.principal_; return ident.principal_; } if(ident.type_ == Authority.Email) { return "Email User"; } if(ident.type_ == Authority.Facebook) { return "Facebook User"; } //we prefer not to say <unknown> anywhere, so principal will be blank //in cases where it would be displayed on the screen and we don't //have anything reasonable to display return ""; } public static Bitmap safeGetContactPicture(final Context context, IdentitiesManager identitiesManager, MIdentity sender) { Bitmap img = null; identitiesManager.getMusubiThumbnail(sender); byte[] thumbnail = sender.musubiThumbnail_; if(thumbnail == null) { identitiesManager.getThumbnail(sender); thumbnail = sender.thumbnail_; } if(thumbnail != null) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true; options.inInputShareable = true; img = BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length, options); } else { //hopefully this implicitly reuses the same one? img = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_contact_picture); } return img; } public static Bitmap safeGetContactThumbnail(final Context context, IdentitiesManager identitiesManager, MIdentity sender) { IdentityCache cache = App.getContactCache(context); CachedIdentity cached = cache.get(sender.id_); return (cached == null) ? null : cached.thumbnail; } public static Bitmap safeGetContactThumbnailWithoutCache(IdentitiesManager identitiesManager, long senderId) { MIdentity sender = identitiesManager.getIdentityWithThumbnailsForId(senderId); byte[] thumbnail = sender.musubiThumbnail_; if(thumbnail == null) { identitiesManager.getThumbnail(sender); thumbnail = sender.thumbnail_; } if(thumbnail != null) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true; options.inInputShareable = true; Bitmap b = BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length, options); return b; } else { return null; } } public static Bitmap getDefaultContactThumbnail(Context context) { //hopefully this implicitly reuses the same one? return BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_contact_picture); } public static boolean addToWhitelistsIfNecessary(FeedManager fm, MyAccountManager am, MIdentity[] members, boolean notify) { boolean needProfilePush = false; LinkedList<MIdentity> owned = FeedManager.getOwners(members); for(MIdentity persona : owned) { MMyAccount provisional_account = am.getProvisionalWhitelistForIdentity(persona.id_); MMyAccount whitelist_account = am.getWhitelistForIdentity(persona.id_); for (MIdentity recipient : members) { needProfilePush |= fm.addToWhitelistsIfNecessary(provisional_account, whitelist_account, persona, recipient); } } if(needProfilePush && notify) { } return needProfilePush; } static final String[] sPositiveAdjectives = { "powerful", "fascinating", "authentic", "loyal", "courageous", "suave", "jubilant", "creative", "masterly", "vivacious", "adorable", "gallant", "earnest", "serene", "superb", "gentle", "captivating", }; static final String[] sAnimals = { "panther", "kitten", "puppy", "lion", "elephant", "mouse", "peacock", "equine", "iguana", "dolphin", "gazelle", "zebra", "ox", "bear", "antelope", "giraffe", "shark", "chipmunk", }; static final String[] sEvents = { "gathering", "party", "date", "performance", "dinner", "movie", "safari", "march", "dessert", "journey", "prize", "victory", "escape", "run", "dance", "flight", "arrival", "departure", }; public static String randomFunName() { Random r = new Random(); String name = ""; name += sPositiveAdjectives[r.nextInt(sPositiveAdjectives.length)] + " "; name += sAnimals[r.nextInt(sAnimals.length)] + " "; name += sEvents[r.nextInt(sEvents.length)]; return name; } //i put this here because when we upgrade the multiidentityselector this is still useful public static Uri addedContact(Context context, Intent data, MultiIdentitySelector identitySelector) { if(data == null || data.getData() == null) { Log.w("uiutil", "unexpected add contact called with blank intent"); return null; } //reread the contact list so that its possible for us to fill in what they typed context.getContentResolver().notifyChange(MusubiService.FORCE_RESCAN_CONTACTS, null); //fetch the actual value of the email and name for the picker Cursor c = context.getContentResolver().query(data.getData(), new String[] { Contacts.DISPLAY_NAME, Contacts._ID }, null, null, null); //TODO: this will require rework for phone number support try { while(c.moveToNext()) { String name = c.getString(0); long id = c.getLong(1); //select the highest id email under the contact Cursor cm = context.getContentResolver().query( ContactsContract.CommonDataKinds.Email.CONTENT_URI, new String[] { ContactsContract.CommonDataKinds.Email.DATA }, ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = ?", new String[]{String.valueOf(id)}, ContactsContract.CommonDataKinds.Email._ID + " DESC"); try { while (cm.moveToNext()) { String email = cm.getString(0); SQLiteOpenHelper databaseSource = App.getDatabaseSource(context); IdentitiesManager identitiesManager = new IdentitiesManager(databaseSource); IBIdentity ibid = new IBIdentity(Authority.Email, email, 0); SQLiteDatabase db = databaseSource.getWritableDatabase(); db.beginTransaction(); long identId = identitiesManager.getIdForIBHashedIdentity(ibid); if(identId == 0) { MIdentity ident = new MIdentity(); ident.type_ = Authority.Email; ident.principal_ = email; ident.principalHash_ = Util.sha256(ident.principal_.getBytes()); ident.principalShortHash_ = Util.shortHash(ident.principalHash_); ident.whitelisted_ = true; ident.name_ = name; identId = identitiesManager.insertIdentity(ident); } db.setTransactionSuccessful(); db.endTransaction(); if(identId > 0) { if(identitySelector != null) identitySelector.addIdentity(name, identId); return MusubiContentProvider.uriForItem(Provided.IDENTITIES, identId); } } } finally { cm.close(); } } } finally { c.close(); } return null; } public static PeopleDetails populatePeopleDetails(Context context, IdentitiesManager im, long[] identityIds, IdentityCache identityCache, PeopleDetails details) { StringBuilder name = new StringBuilder(40); boolean noNames = true; List<Bitmap> thumbnails = details.images; thumbnails.clear(); int unownedidentities = 0; for (long id : identityIds) { CachedIdentity cached = identityCache.get(id); if (cached == null || cached.midentity.owned_) { continue; } unownedidentities++; if (thumbnails.size() >= 4) { continue; } if (cached.hasThumbnail) { if (!noNames) { name.append(", "); } else { noNames = false; } name.append(cached.name); thumbnails.add(cached.thumbnail); } } if (thumbnails.size() == 0 && identityIds.length > 0) { // no thumbnail, just use first guy CachedIdentity cached = identityCache.get(identityIds[0]); name.append(cached.name); thumbnails.add(cached.thumbnail); unownedidentities++; } if (unownedidentities > thumbnails.size()) { name.append(" and ").append(unownedidentities - thumbnails.size()).append(" more"); } details.name = name.toString(); return details; } public static class PeopleDetails { public final List<Bitmap> images = new ArrayList<Bitmap>(); public String name; } private static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { if (width > height) { inSampleSize = Math.round((float)height / (float)reqHeight); } else { inSampleSize = Math.round((float)width / (float)reqWidth); } } return inSampleSize; } public static Bitmap decodeSampledBitmapFromByteArray(byte[] bytes, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options); } }