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