• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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. */
instance()69     public static EasConnectionCache instance() {
70         if (sCache == null) {
71             sCache = new EasConnectionCache();
72         }
73         return sCache;
74     }
75 
EasConnectionCache()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      */
createConnectionManager(final Context context, final HostAuth hostAuth)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      */
getCachedConnectionManager( final Context context, final HostAuth hostAuth)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      */
getConnectionManager( final Context context, final HostAuth hostAuth)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      */
uncacheConnectionManager(final HostAuth hostAuth)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