1e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdonpackage com.android.email.mail.internet; 2e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon 3e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdonimport android.content.Context; 4e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdonimport android.text.format.DateUtils; 5e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon 6e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdonimport com.android.email.mail.internet.OAuthAuthenticator.AuthenticationResult; 7e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdonimport com.android.emailcommon.Logging; 8e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdonimport com.android.emailcommon.mail.AuthenticationFailedException; 9e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdonimport com.android.emailcommon.mail.MessagingException; 10e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdonimport com.android.emailcommon.provider.Account; 11e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdonimport com.android.emailcommon.provider.Credential; 12e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdonimport com.android.emailcommon.provider.HostAuth; 13e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdonimport com.android.mail.utils.LogUtils; 14e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon 15e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdonimport java.io.IOException; 16e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdonimport java.util.HashMap; 17e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdonimport java.util.Map; 18e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon 19e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdonpublic class AuthenticationCache { 20e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon private static AuthenticationCache sCache; 21e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon 22e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon // Threshold for refreshing a token. If the token is expected to expire within this amount of 23e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon // time, we won't even bother attempting to use it and will simply force a refresh. 24e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon private static final long EXPIRATION_THRESHOLD = 5 * DateUtils.MINUTE_IN_MILLIS; 25e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon 26e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon private final Map<Long, CacheEntry> mCache; 27e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon private final OAuthAuthenticator mAuthenticator; 28e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon 29e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon private class CacheEntry { 30e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon CacheEntry(long accountId, String providerId, String accessToken, String refreshToken, 31e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon long expirationTime) { 32e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon mAccountId = accountId; 33e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon mProviderId = providerId; 34e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon mAccessToken = accessToken; 35e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon mRefreshToken = refreshToken; 36e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon mExpirationTime = expirationTime; 37e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 38e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon 39e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon final long mAccountId; 40e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon String mProviderId; 41e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon String mAccessToken; 42e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon String mRefreshToken; 43e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon long mExpirationTime; 44e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 45e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon 46e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon public static AuthenticationCache getInstance() { 47e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon synchronized (AuthenticationCache.class) { 48e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon if (sCache == null) { 49e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon sCache = new AuthenticationCache(); 50e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 51e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon return sCache; 52e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 53e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 54e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon 55e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon private AuthenticationCache() { 56e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon mCache = new HashMap<Long, CacheEntry>(); 57e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon mAuthenticator = new OAuthAuthenticator(); 58e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 59e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon 60e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon // Gets an access token for the given account. This may be whatever is currently cached, or 61e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon // it may query the server to get a new one if the old one is expired or nearly expired. 62e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon public String retrieveAccessToken(Context context, Account account) throws 63e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon MessagingException, IOException { 64e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon // Currently, we always use the same OAuth info for both sending and receiving. 65e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon // If we start to allow different credential objects for sending and receiving, this 66e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon // will need to be updated. 67e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon CacheEntry entry = null; 68e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon synchronized (mCache) { 69e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon entry = getEntry(context, account); 70e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 71e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon synchronized (entry) { 72e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon final long actualExpiration = entry.mExpirationTime - EXPIRATION_THRESHOLD; 73e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon if (System.currentTimeMillis() > actualExpiration) { 74e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon // This access token is pretty close to end of life. Don't bother trying to use it, 75e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon // it might just time out while we're trying to sync. Go ahead and refresh it 76e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon // immediately. 77e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon refreshEntry(context, entry); 78e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 79e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon return entry.mAccessToken; 80e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 81e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 82e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon 83e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon public String refreshAccessToken(Context context, Account account) throws 84e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon MessagingException, IOException { 85e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon CacheEntry entry = getEntry(context, account); 86e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon synchronized (entry) { 87e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon refreshEntry(context, entry); 88e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon return entry.mAccessToken; 89e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 90e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 91e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon 92e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon private CacheEntry getEntry(Context context, Account account) { 93e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon CacheEntry entry; 94994c282d804a635f783681ae314a6b4b244b476eTony Mantler if (account.isSaved() && !account.isTemporary()) { 95e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon entry = mCache.get(account.mId); 96e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon if (entry == null) { 97e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon LogUtils.d(Logging.LOG_TAG, "initializing entry from database"); 98e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon final HostAuth hostAuth = account.getOrCreateHostAuthRecv(context); 99e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon final Credential credential = hostAuth.getOrCreateCredential(context); 100e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon entry = new CacheEntry(account.mId, credential.mProviderId, credential.mAccessToken, 101e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon credential.mRefreshToken, credential.mExpiration); 102e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon mCache.put(account.mId, entry); 103e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 104e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } else { 105994c282d804a635f783681ae314a6b4b244b476eTony Mantler // This account is temporary, just create a temporary entry. Don't store 106e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon // it in the cache, it won't be findable because we don't yet have an account Id. 107e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon final HostAuth hostAuth = account.getOrCreateHostAuthRecv(context); 108e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon final Credential credential = hostAuth.getCredential(context); 109e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon entry = new CacheEntry(account.mId, credential.mProviderId, credential.mAccessToken, 110e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon credential.mRefreshToken, credential.mExpiration); 111e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 112e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon return entry; 113e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 114e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon 115e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon private void refreshEntry(Context context, CacheEntry entry) throws 116e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon IOException, MessagingException { 117e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon LogUtils.d(Logging.LOG_TAG, "AuthenticationCache refreshEntry %d", entry.mAccountId); 118e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon try { 119e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon final AuthenticationResult result = mAuthenticator.requestRefresh(context, 120e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon entry.mProviderId, entry.mRefreshToken); 121e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon // Don't set the refresh token here, it's not returned by the refresh response, 122e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon // so setting it here would make it blank. 123e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon entry.mAccessToken = result.mAccessToken; 124e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon entry.mExpirationTime = result.mExpiresInSeconds * DateUtils.SECOND_IN_MILLIS + 125e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon System.currentTimeMillis(); 126e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon saveEntry(context, entry); 127e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } catch (AuthenticationFailedException e) { 128e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon // This is fatal. Clear the tokens and rethrow the exception. 129e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon LogUtils.d(Logging.LOG_TAG, "authentication failed, clearning"); 130e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon clearEntry(context, entry); 131e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon throw e; 132e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } catch (MessagingException e) { 133e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon LogUtils.d(Logging.LOG_TAG, "messaging exception"); 134e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon throw e; 135e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } catch (IOException e) { 136e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon LogUtils.d(Logging.LOG_TAG, "IO exception"); 137e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon throw e; 138e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 139e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 140e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon 141e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon private void saveEntry(Context context, CacheEntry entry) { 142e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon LogUtils.d(Logging.LOG_TAG, "saveEntry"); 143e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon 144e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon final Account account = Account.restoreAccountWithId(context, entry.mAccountId); 145e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon final HostAuth hostAuth = account.getOrCreateHostAuthRecv(context); 146e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon final Credential cred = hostAuth.getOrCreateCredential(context); 147e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon cred.mProviderId = entry.mProviderId; 148e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon cred.mAccessToken = entry.mAccessToken; 149e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon cred.mRefreshToken = entry.mRefreshToken; 150e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon cred.mExpiration = entry.mExpirationTime; 151e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon cred.update(context, cred.toContentValues()); 152e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 153e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon 154e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon private void clearEntry(Context context, CacheEntry entry) { 155e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon LogUtils.d(Logging.LOG_TAG, "clearEntry"); 156e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon entry.mAccessToken = ""; 157e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon entry.mRefreshToken = ""; 158e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon entry.mExpirationTime = 0; 159e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon saveEntry(context, entry); 160994c282d804a635f783681ae314a6b4b244b476eTony Mantler mCache.remove(entry.mAccountId); 161e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon } 162e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon} 163