/* * Copyright (C) 2013 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.exchange.eas; import android.content.Context; import android.text.format.DateUtils; import com.android.emailcommon.provider.HostAuth; import com.android.emailcommon.utility.EmailClientConnectionManager; import com.android.exchange.Eas; import com.android.mail.utils.LogUtils; import org.apache.http.conn.params.ConnManagerPNames; import org.apache.http.conn.params.ConnPerRoute; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpParams; import java.security.cert.CertificateException; import java.util.HashMap; /** * Manage all {@link EmailClientConnectionManager}s used by Exchange operations. * When making connections for persisted accounts, this class will cache and reuse connections * as much as possible. All access of connection objects should accordingly go through this class. * * We use {@link HostAuth}'s id as the cache key. Multiple calls to {@link #getConnectionManager} * with {@link HostAuth} objects with the same id will get the same connection object returned, * i.e. we assume that the rest of the contents of the {@link HostAuth} objects are also the same, * not just the id. If the {@link HostAuth} changes or is deleted, {@link #uncacheConnectionManager} * must be called. * * This cache is a singleton since the whole point is to not have multiples. */ public class EasConnectionCache { /** The max length of time we want to keep a connection in the cache. */ private static final long MAX_LIFETIME = 10 * DateUtils.MINUTE_IN_MILLIS; private final HashMap mConnectionMap; /** The creation time of connections in mConnectionMap. */ private final HashMap mConnectionCreationTimes; private static final ConnPerRoute sConnPerRoute = new ConnPerRoute() { @Override public int getMaxForRoute(final HttpRoute route) { return 8; } }; /** The singleton instance of the cache. */ private static EasConnectionCache sCache = null; /** Accessor for the cache singleton. */ public static EasConnectionCache instance() { if (sCache == null) { sCache = new EasConnectionCache(); } return sCache; } private EasConnectionCache() { mConnectionMap = new HashMap(); mConnectionCreationTimes = new HashMap(); } /** * Create an {@link EmailClientConnectionManager} for this {@link HostAuth}. * @param context The {@link Context}. * @param hostAuth The {@link HostAuth} to which we want to connect. * @return The {@link EmailClientConnectionManager} for hostAuth. */ private EmailClientConnectionManager createConnectionManager(final Context context, final HostAuth hostAuth) throws CertificateException { LogUtils.d(Eas.LOG_TAG, "Creating new connection manager for HostAuth %d", hostAuth.mId); final HttpParams params = new BasicHttpParams(); params.setIntParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 25); params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, sConnPerRoute); final EmailClientConnectionManager mgr = EmailClientConnectionManager.newInstance(context, params, hostAuth); mgr.registerClientCert(context, hostAuth); return mgr; } /** * Get the correct {@link EmailClientConnectionManager} for a {@link HostAuth} from our cache. * If it's not in the cache, create and add it. * If the cached connection is old, recreate it. * @param context The {@link Context}. * @param hostAuth The {@link HostAuth} to which we want to connect. * @return The {@link EmailClientConnectionManager} for hostAuth. */ private synchronized EmailClientConnectionManager getCachedConnectionManager( final Context context, final HostAuth hostAuth) throws CertificateException { EmailClientConnectionManager connectionManager = mConnectionMap.get(hostAuth.mId); final long now = System.currentTimeMillis(); if (connectionManager != null) { final long lifetime = now - mConnectionCreationTimes.get(hostAuth.mId); if (lifetime > MAX_LIFETIME) { LogUtils.d(Eas.LOG_TAG, "Aging out connection manager for HostAuth %d", hostAuth.mId); uncacheConnectionManager(hostAuth); connectionManager = null; } } if (connectionManager == null) { connectionManager = createConnectionManager(context, hostAuth); mConnectionMap.put(hostAuth.mId, connectionManager); mConnectionCreationTimes.put(hostAuth.mId, now); } else { LogUtils.d(Eas.LOG_TAG, "Reusing cached connection manager for HostAuth %d", hostAuth.mId); } return connectionManager; } /** * Get the correct {@link EmailClientConnectionManager} for a {@link HostAuth}. If the * {@link HostAuth} is persistent, then use the cache for this request. * @param context The {@link Context}. * @param hostAuth The {@link HostAuth} to which we want to connect. * @return The {@link EmailClientConnectionManager} for hostAuth. */ public EmailClientConnectionManager getConnectionManager( final Context context, final HostAuth hostAuth) throws CertificateException { final EmailClientConnectionManager connectionManager; // We only cache the connection manager for persisted HostAuth objects, i.e. objects // whose ids are permanent and won't get reused by other transient HostAuth objects. if (hostAuth.isSaved()) { connectionManager = getCachedConnectionManager(context, hostAuth); } else { connectionManager = createConnectionManager(context, hostAuth); } return connectionManager; } /** * Remove a connection manager from the cache. This is necessary when a {@link HostAuth} is * redirected or otherwise altered. It's not strictly necessary but good to also call this * when a {@link HostAuth} is deleted, i.e. when an account is removed. * @param hostAuth The {@link HostAuth} whose connection manager should be deleted. */ public synchronized void uncacheConnectionManager(final HostAuth hostAuth) { LogUtils.d(Eas.LOG_TAG, "Uncaching connection manager for HostAuth %d", hostAuth.mId); EmailClientConnectionManager connectionManager = mConnectionMap.get(hostAuth.mId); if (connectionManager != null) { connectionManager.shutdown(); } mConnectionMap.remove(hostAuth.mId); mConnectionCreationTimes.remove(hostAuth.mId); } }