/* * Copyright (C) 2012 The Android Open Source Project * * 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 com.android.mail.photomanager; import android.content.ContentResolver; import android.content.Context; import android.text.TextUtils; import android.util.LruCache; import com.android.mail.ContactInfo; import com.android.mail.SenderInfoLoader; import com.android.mail.ui.ImageCanvas; import com.android.mail.utils.LogUtils; import com.google.common.base.Objects; import com.google.common.collect.ImmutableMap; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Asynchronously loads contact photos and maintains a cache of photos. */ public class ContactPhotoManager extends PhotoManager { public static final String CONTACT_PHOTO_SERVICE = "contactPhotos"; /** * An LRU cache for photo ids mapped to contact addresses. */ private final LruCache mPhotoIdCache; private final LetterTileProvider mLetterTileProvider; /** Cache size for {@link #mPhotoIdCache}. Starting with 500 entries. */ private static final int PHOTO_ID_CACHE_SIZE = 500; /** * Requests the singleton instance with data bound from the available authenticators. This * method can safely be called from the UI thread. */ public static ContactPhotoManager getInstance(Context context) { Context applicationContext = context.getApplicationContext(); ContactPhotoManager service = (ContactPhotoManager) applicationContext.getSystemService(CONTACT_PHOTO_SERVICE); if (service == null) { service = createContactPhotoManager(applicationContext); LogUtils.e(TAG, "No contact photo service in context: " + applicationContext); } return service; } public static synchronized ContactPhotoManager createContactPhotoManager(Context context) { return new ContactPhotoManager(context); } public static int generateHash(ImageCanvas view, int pos, Object key) { return Objects.hashCode(view, pos, key); } private ContactPhotoManager(Context context) { super(context); mPhotoIdCache = new LruCache(PHOTO_ID_CACHE_SIZE); mLetterTileProvider = new LetterTileProvider(context); } @Override protected DefaultImageProvider getDefaultImageProvider() { return mLetterTileProvider; } @Override protected int getHash(PhotoIdentifier id, ImageCanvas view) { final ContactIdentifier contactId = (ContactIdentifier) id; return generateHash(view, contactId.pos, contactId.getKey()); } @Override protected PhotoLoaderThread getLoaderThread(ContentResolver contentResolver) { return new ContactPhotoLoaderThread(contentResolver); } @Override public void clear() { super.clear(); mPhotoIdCache.evictAll(); } public static class ContactIdentifier extends PhotoIdentifier { public final String name; public final String emailAddress; public final int pos; public ContactIdentifier(String name, String emailAddress, int pos) { this.name = name; this.emailAddress = emailAddress; this.pos = pos; } @Override public boolean isValid() { return !TextUtils.isEmpty(emailAddress); } @Override public Object getKey() { return emailAddress; } @Override public int hashCode() { int hash = 17; hash = 31 * hash + (emailAddress != null ? emailAddress.hashCode() : 0); hash = 31 * hash + (name != null ? name.hashCode() : 0); hash = 31 * hash + pos; return hash; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ContactIdentifier other = (ContactIdentifier) obj; return Objects.equal(emailAddress, other.emailAddress) && Objects.equal(name, other.name) && Objects.equal(pos, other.pos); } @Override public String toString() { final StringBuilder sb = new StringBuilder("{"); sb.append(super.toString()); sb.append(" name="); sb.append(name); sb.append(" email="); sb.append(emailAddress); sb.append(" pos="); sb.append(pos); sb.append("}"); return sb.toString(); } @Override public int compareTo(PhotoIdentifier another) { return 0; } } public class ContactPhotoLoaderThread extends PhotoLoaderThread { public ContactPhotoLoaderThread(ContentResolver resolver) { super(resolver); } @Override protected Map loadPhotos(Collection requests) { Map photos = new HashMap(requests.size()); Set addresses = new HashSet(); Set photoIds = new HashSet(); HashMap photoIdMap = new HashMap(); Long match; String emailAddress; for (Request request : requests) { emailAddress = (String) request.getKey(); match = mPhotoIdCache.get(emailAddress); if (match != null) { photoIds.add(match); photoIdMap.put(match, emailAddress); } else { addresses.add(emailAddress); } } // get the Map of email addresses to ContactInfo ImmutableMap emailAddressToContactInfoMap = SenderInfoLoader.loadContactPhotos( getResolver(), addresses, false /* decodeBitmaps */); // Put all entries into photos map: a mapping of email addresses to photoBytes. // If there is no ContactInfo, it means we couldn't get a photo for this // address so just put null in for the bytes so that the crazy caching // works properly and we don't get an infinite loop of GC churn. if (emailAddressToContactInfoMap != null) { for (final String address : addresses) { final ContactInfo info = emailAddressToContactInfoMap.get(address); photos.put(address, new BitmapHolder(info != null ? info.photoBytes : null, -1, -1)); } } else { // Still need to set a null result for all addresses, otherwise we end // up in the loop where photo manager attempts to load these again. for (final String address: addresses) { photos.put(address, new BitmapHolder(null, -1, -1)); } } return photos; } } }