Home | History | Annotate | Download | only in photomanager
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.mail.photomanager;
     18 
     19 import android.content.ContentResolver;
     20 import android.content.Context;
     21 import android.text.TextUtils;
     22 import android.util.LruCache;
     23 
     24 import com.android.mail.ContactInfo;
     25 import com.android.mail.SenderInfoLoader;
     26 import com.android.mail.ui.ImageCanvas;
     27 import com.android.mail.utils.LogUtils;
     28 import com.google.common.base.Objects;
     29 import com.google.common.collect.ImmutableMap;
     30 
     31 import java.util.Collection;
     32 import java.util.HashMap;
     33 import java.util.HashSet;
     34 import java.util.Map;
     35 import java.util.Set;
     36 
     37 /**
     38  * Asynchronously loads contact photos and maintains a cache of photos.
     39  */
     40 public class ContactPhotoManager extends PhotoManager {
     41     public static final String CONTACT_PHOTO_SERVICE = "contactPhotos";
     42 
     43     /**
     44      * An LRU cache for photo ids mapped to contact addresses.
     45      */
     46     private final LruCache<String, Long> mPhotoIdCache;
     47     private final LetterTileProvider mLetterTileProvider;
     48 
     49     /** Cache size for {@link #mPhotoIdCache}. Starting with 500 entries. */
     50     private static final int PHOTO_ID_CACHE_SIZE = 500;
     51 
     52     /**
     53      * Requests the singleton instance with data bound from the available authenticators. This
     54      * method can safely be called from the UI thread.
     55      */
     56     public static ContactPhotoManager getInstance(Context context) {
     57         Context applicationContext = context.getApplicationContext();
     58         ContactPhotoManager service =
     59                 (ContactPhotoManager) applicationContext.getSystemService(CONTACT_PHOTO_SERVICE);
     60         if (service == null) {
     61             service = createContactPhotoManager(applicationContext);
     62             LogUtils.e(TAG, "No contact photo service in context: " + applicationContext);
     63         }
     64         return service;
     65     }
     66 
     67     public static synchronized ContactPhotoManager createContactPhotoManager(Context context) {
     68         return new ContactPhotoManager(context);
     69     }
     70 
     71     public static int generateHash(ImageCanvas view, int pos, Object key) {
     72         return Objects.hashCode(view, pos, key);
     73     }
     74 
     75     private ContactPhotoManager(Context context) {
     76         super(context);
     77         mPhotoIdCache = new LruCache<String, Long>(PHOTO_ID_CACHE_SIZE);
     78         mLetterTileProvider = new LetterTileProvider(context);
     79     }
     80 
     81     @Override
     82     protected DefaultImageProvider getDefaultImageProvider() {
     83         return mLetterTileProvider;
     84     }
     85 
     86     @Override
     87     protected int getHash(PhotoIdentifier id, ImageCanvas view) {
     88         final ContactIdentifier contactId = (ContactIdentifier) id;
     89         return generateHash(view, contactId.pos, contactId.getKey());
     90     }
     91 
     92     @Override
     93     protected PhotoLoaderThread getLoaderThread(ContentResolver contentResolver) {
     94         return new ContactPhotoLoaderThread(contentResolver);
     95     }
     96 
     97     @Override
     98     public void clear() {
     99         super.clear();
    100         mPhotoIdCache.evictAll();
    101     }
    102 
    103     public static class ContactIdentifier extends PhotoIdentifier {
    104         public final String name;
    105         public final String emailAddress;
    106         public final int pos;
    107 
    108         public ContactIdentifier(String name, String emailAddress, int pos) {
    109             this.name = name;
    110             this.emailAddress = emailAddress;
    111             this.pos = pos;
    112         }
    113 
    114         @Override
    115         public boolean isValid() {
    116             return !TextUtils.isEmpty(emailAddress);
    117         }
    118 
    119         @Override
    120         public Object getKey() {
    121             return emailAddress;
    122         }
    123 
    124         @Override
    125         public int hashCode() {
    126             int hash = 17;
    127             hash = 31 * hash + (emailAddress != null ? emailAddress.hashCode() : 0);
    128             hash = 31 * hash + (name != null ? name.hashCode() : 0);
    129             hash = 31 * hash + pos;
    130             return hash;
    131         }
    132 
    133         @Override
    134         public boolean equals(Object obj) {
    135             if (this == obj)
    136                 return true;
    137             if (obj == null)
    138                 return false;
    139             if (getClass() != obj.getClass())
    140                 return false;
    141             ContactIdentifier other = (ContactIdentifier) obj;
    142             return Objects.equal(emailAddress, other.emailAddress)
    143                     && Objects.equal(name, other.name) && Objects.equal(pos, other.pos);
    144         }
    145 
    146         @Override
    147         public String toString() {
    148             final StringBuilder sb = new StringBuilder("{");
    149             sb.append(super.toString());
    150             sb.append(" name=");
    151             sb.append(name);
    152             sb.append(" email=");
    153             sb.append(emailAddress);
    154             sb.append(" pos=");
    155             sb.append(pos);
    156             sb.append("}");
    157             return sb.toString();
    158         }
    159 
    160         @Override
    161         public int compareTo(PhotoIdentifier another) {
    162             return 0;
    163         }
    164     }
    165 
    166     public class ContactPhotoLoaderThread extends PhotoLoaderThread {
    167         public ContactPhotoLoaderThread(ContentResolver resolver) {
    168             super(resolver);
    169         }
    170 
    171         @Override
    172         protected Map<String, BitmapHolder> loadPhotos(Collection<Request> requests) {
    173             Map<String, BitmapHolder> photos = new HashMap<String, BitmapHolder>(requests.size());
    174 
    175             Set<String> addresses = new HashSet<String>();
    176             Set<Long> photoIds = new HashSet<Long>();
    177             HashMap<Long, String> photoIdMap = new HashMap<Long, String>();
    178 
    179             Long match;
    180             String emailAddress;
    181             for (Request request : requests) {
    182                 emailAddress = (String) request.getKey();
    183                 match = mPhotoIdCache.get(emailAddress);
    184                 if (match != null) {
    185                     photoIds.add(match);
    186                     photoIdMap.put(match, emailAddress);
    187                 } else {
    188                     addresses.add(emailAddress);
    189                 }
    190             }
    191 
    192             // get the Map of email addresses to ContactInfo
    193             ImmutableMap<String, ContactInfo> emailAddressToContactInfoMap =
    194                     SenderInfoLoader.loadContactPhotos(
    195                     getResolver(), addresses, false /* decodeBitmaps */);
    196 
    197             // Put all entries into photos map: a mapping of email addresses to photoBytes.
    198             // If there is no ContactInfo, it means we couldn't get a photo for this
    199             // address so just put null in for the bytes so that the crazy caching
    200             // works properly and we don't get an infinite loop of GC churn.
    201             if (emailAddressToContactInfoMap != null) {
    202                 for (final String address : addresses) {
    203                     final ContactInfo info = emailAddressToContactInfoMap.get(address);
    204                     photos.put(address,
    205                             new BitmapHolder(info != null ? info.photoBytes : null, -1, -1));
    206                 }
    207             } else {
    208                 // Still need to set a null result for all addresses, otherwise we end
    209                 // up in the loop where photo manager attempts to load these again.
    210                 for (final String address: addresses) {
    211                     photos.put(address, new BitmapHolder(null, -1, -1));
    212                 }
    213             }
    214 
    215             return photos;
    216         }
    217     }
    218 }
    219