Home | History | Annotate | Download | only in eas
      1 /*
      2  * Copyright (C) 2013 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.exchange.eas;
     18 
     19 import android.content.Context;
     20 import android.text.format.DateUtils;
     21 
     22 import com.android.emailcommon.provider.HostAuth;
     23 import com.android.emailcommon.utility.EmailClientConnectionManager;
     24 import com.android.exchange.Eas;
     25 import com.android.mail.utils.LogUtils;
     26 
     27 import org.apache.http.conn.params.ConnManagerPNames;
     28 import org.apache.http.conn.params.ConnPerRoute;
     29 import org.apache.http.conn.routing.HttpRoute;
     30 import org.apache.http.params.BasicHttpParams;
     31 import org.apache.http.params.HttpParams;
     32 
     33 import java.security.cert.CertificateException;
     34 import java.util.HashMap;
     35 
     36 /**
     37  * Manage all {@link EmailClientConnectionManager}s used by Exchange operations.
     38  * When making connections for persisted accounts, this class will cache and reuse connections
     39  * as much as possible. All access of connection objects should accordingly go through this class.
     40  *
     41  * We use {@link HostAuth}'s id as the cache key. Multiple calls to {@link #getConnectionManager}
     42  * with {@link HostAuth} objects with the same id will get the same connection object returned,
     43  * i.e. we assume that the rest of the contents of the {@link HostAuth} objects are also the same,
     44  * not just the id. If the {@link HostAuth} changes or is deleted, {@link #uncacheConnectionManager}
     45  * must be called.
     46  *
     47  * This cache is a singleton since the whole point is to not have multiples.
     48  */
     49 public class EasConnectionCache {
     50 
     51     /** The max length of time we want to keep a connection in the cache. */
     52     private static final long MAX_LIFETIME = 10 * DateUtils.MINUTE_IN_MILLIS;
     53 
     54     private final HashMap<Long, EmailClientConnectionManager> mConnectionMap;
     55     /** The creation time of connections in mConnectionMap. */
     56     private final HashMap<Long, Long> mConnectionCreationTimes;
     57 
     58     private static final ConnPerRoute sConnPerRoute = new ConnPerRoute() {
     59         @Override
     60         public int getMaxForRoute(final HttpRoute route) {
     61             return 8;
     62         }
     63     };
     64 
     65     /** The singleton instance of the cache. */
     66     private static EasConnectionCache sCache = null;
     67 
     68     /** Accessor for the cache singleton. */
     69     public static EasConnectionCache instance() {
     70         if (sCache == null) {
     71             sCache = new EasConnectionCache();
     72         }
     73         return sCache;
     74     }
     75 
     76     private EasConnectionCache() {
     77         mConnectionMap = new HashMap<Long, EmailClientConnectionManager>();
     78         mConnectionCreationTimes = new HashMap<Long, Long>();
     79     }
     80 
     81     /**
     82      * Create an {@link EmailClientConnectionManager} for this {@link HostAuth}.
     83      * @param context The {@link Context}.
     84      * @param hostAuth The {@link HostAuth} to which we want to connect.
     85      * @return The {@link EmailClientConnectionManager} for hostAuth.
     86      */
     87     private EmailClientConnectionManager createConnectionManager(final Context context,
     88             final HostAuth hostAuth)
     89             throws CertificateException {
     90         LogUtils.d(Eas.LOG_TAG, "Creating new connection manager for HostAuth %d", hostAuth.mId);
     91         final HttpParams params = new BasicHttpParams();
     92         params.setIntParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 25);
     93         params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, sConnPerRoute);
     94         final EmailClientConnectionManager mgr =
     95                 EmailClientConnectionManager.newInstance(context, params, hostAuth);
     96 
     97         mgr.registerClientCert(context, hostAuth);
     98 
     99         return mgr;
    100     }
    101 
    102     /**
    103      * Get the correct {@link EmailClientConnectionManager} for a {@link HostAuth} from our cache.
    104      * If it's not in the cache, create and add it.
    105      * If the cached connection is old, recreate it.
    106      * @param context The {@link Context}.
    107      * @param hostAuth The {@link HostAuth} to which we want to connect.
    108      * @return The {@link EmailClientConnectionManager} for hostAuth.
    109      */
    110     private synchronized EmailClientConnectionManager getCachedConnectionManager(
    111             final Context context, final HostAuth hostAuth)
    112             throws CertificateException {
    113         EmailClientConnectionManager connectionManager = mConnectionMap.get(hostAuth.mId);
    114         final long now = System.currentTimeMillis();
    115         if (connectionManager != null) {
    116             final long lifetime = now - mConnectionCreationTimes.get(hostAuth.mId);
    117             if (lifetime > MAX_LIFETIME) {
    118                 LogUtils.d(Eas.LOG_TAG, "Aging out connection manager for HostAuth %d",
    119                         hostAuth.mId);
    120                 uncacheConnectionManager(hostAuth);
    121                 connectionManager = null;
    122             }
    123         }
    124         if (connectionManager == null) {
    125             connectionManager = createConnectionManager(context, hostAuth);
    126             mConnectionMap.put(hostAuth.mId, connectionManager);
    127             mConnectionCreationTimes.put(hostAuth.mId, now);
    128         } else {
    129             LogUtils.d(Eas.LOG_TAG, "Reusing cached connection manager for HostAuth %d",
    130                     hostAuth.mId);
    131         }
    132         return connectionManager;
    133     }
    134 
    135     /**
    136      * Get the correct {@link EmailClientConnectionManager} for a {@link HostAuth}. If the
    137      * {@link HostAuth} is persistent, then use the cache for this request.
    138      * @param context The {@link Context}.
    139      * @param hostAuth The {@link HostAuth} to which we want to connect.
    140      * @return The {@link EmailClientConnectionManager} for hostAuth.
    141      */
    142     public EmailClientConnectionManager getConnectionManager(
    143             final Context context, final HostAuth hostAuth)
    144             throws CertificateException {
    145         final EmailClientConnectionManager connectionManager;
    146         // We only cache the connection manager for persisted HostAuth objects, i.e. objects
    147         // whose ids are permanent and won't get reused by other transient HostAuth objects.
    148         if (hostAuth.isSaved()) {
    149             connectionManager = getCachedConnectionManager(context, hostAuth);
    150         } else {
    151             connectionManager = createConnectionManager(context, hostAuth);
    152         }
    153         return connectionManager;
    154     }
    155 
    156     /**
    157      * Remove a connection manager from the cache. This is necessary when a {@link HostAuth} is
    158      * redirected or otherwise altered. It's not strictly necessary but good to also call this
    159      * when a {@link HostAuth} is deleted, i.e. when an account is removed.
    160      * @param hostAuth The {@link HostAuth} whose connection manager should be deleted.
    161      */
    162     public synchronized void uncacheConnectionManager(final HostAuth hostAuth) {
    163         LogUtils.d(Eas.LOG_TAG, "Uncaching connection manager for HostAuth %d", hostAuth.mId);
    164         EmailClientConnectionManager connectionManager = mConnectionMap.get(hostAuth.mId);
    165         if (connectionManager != null) {
    166             connectionManager.shutdown();
    167         }
    168         mConnectionMap.remove(hostAuth.mId);
    169         mConnectionCreationTimes.remove(hostAuth.mId);
    170     }
    171 }
    172