001package com.gigya.android.sdk.session; 002 003import android.app.Activity; 004import android.app.Application; 005import android.content.Intent; 006import android.os.Bundle; 007import android.support.v4.content.LocalBroadcastManager; 008 009import com.gigya.android.sdk.Config; 010import com.gigya.android.sdk.GigyaDefinitions; 011import com.gigya.android.sdk.GigyaInterceptor; 012import com.gigya.android.sdk.GigyaLogger; 013import com.gigya.android.sdk.account.IAccountService; 014import com.gigya.android.sdk.api.ApiService; 015import com.gigya.android.sdk.api.GigyaApiRequest; 016import com.gigya.android.sdk.api.GigyaApiResponse; 017import com.gigya.android.sdk.api.IApiRequestFactory; 018import com.gigya.android.sdk.api.IApiService; 019import com.gigya.android.sdk.network.GigyaError; 020import com.gigya.android.sdk.network.adapter.RestAdapter; 021import com.gigya.android.sdk.ui.Presenter; 022 023import java.util.Date; 024import java.util.HashMap; 025import java.util.Map; 026import java.util.Timer; 027import java.util.TimerTask; 028import java.util.concurrent.TimeUnit; 029 030public class SessionVerificationService implements ISessionVerificationService { 031 032 private static final String LOG_TAG = "SessionVerificationService"; 033 034 final private Application _context; 035 final private Config _config; 036 final private ISessionService _sessionService; 037 final private IAccountService _accountService; 038 final private IApiService _apiService; 039 final private IApiRequestFactory _requestFactory; 040 041 public SessionVerificationService(Application context, 042 Config config, 043 ISessionService sessionService, 044 IAccountService accountService, 045 IApiService apiService, 046 IApiRequestFactory requestFactory) { 047 _context = context; 048 _config = config; 049 _sessionService = sessionService; 050 _accountService = accountService; 051 _apiService = apiService; 052 _requestFactory = requestFactory; 053 054 /* 055 Add a setSession interception in order to make sure that the service starts when a new 056 Session is being set. 057 */ 058 _sessionService.addInterceptor(new GigyaInterceptor("VERIFY_LOGIN") { 059 @Override 060 public void intercept() { 061 updateInterval(); 062 if (_sessionService.isValid() && _verificationInterval != 0) { 063 restart(); 064 } 065 } 066 }); 067 } 068 069 private long _verificationInterval; 070 private long _lastRequestTimestamp = 0; 071 private Timer _timer; 072 073 @Override 074 public void updateInterval() { 075 /* 076 Update the current interval as set in the configuration. 077 */ 078 _verificationInterval = TimeUnit.SECONDS.toMillis(_config.getSessionVerificationInterval()); 079 } 080 081 @Override 082 public void registerActivityLifecycleCallbacks() { 083 _context.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { 084 085 private int activityReferences = 0; 086 private boolean isActivityChangingConfigurations = false; 087 088 @Override 089 public void onActivityCreated(Activity activity, Bundle bundle) { 090 // Stub. 091 } 092 093 @Override 094 public void onActivityStarted(Activity activity) { 095 if (++activityReferences == 1 && !isActivityChangingConfigurations) { 096 097 // App enters foreground 098 GigyaLogger.info(LOG_TAG, "Application lifecycle - Foreground"); 099 if (_sessionService.isValid()) { 100 // Will start session countdown timer if the current session contains an expiration time. 101 _sessionService.startSessionCountdownTimerIfNeeded(); 102 // Make sure interval is updated correctly. 103 updateInterval(); 104 // Session verification is only relevant when user is logged in. 105 start(); 106 } 107 } 108 } 109 110 @Override 111 public void onActivityResumed(Activity activity) { 112 // Stub. Can track the current resumed activity. 113 } 114 115 @Override 116 public void onActivityPaused(Activity activity) { 117 // Stub. 118 } 119 120 @Override 121 public void onActivityStopped(Activity activity) { 122 isActivityChangingConfigurations = activity.isChangingConfigurations(); 123 if (--activityReferences == 0 && !isActivityChangingConfigurations) { 124 // App enters background 125 GigyaLogger.info(LOG_TAG, "Application lifecycle - Background"); 126 // Make sure to cancel the session expiration countdown timer (if live). 127 _sessionService.cancelSessionCountdownTimer(); 128 stop(); 129 } 130 } 131 132 @Override 133 public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { 134 // Stub. 135 } 136 137 @Override 138 public void onActivityDestroyed(Activity activity) { 139 if (--activityReferences == 0 && !isActivityChangingConfigurations) { 140 // Flush the Presenter statics just in case. When all activities have been destroyed. 141 Presenter.flush(); 142 } 143 } 144 }); 145 } 146 147 @Override 148 public void start() { 149 if (_verificationInterval == 0) { 150 GigyaLogger.debug(LOG_TAG, "start: Verification interval is 0. Verification flow irrelevant"); 151 return; 152 } 153 GigyaLogger.debug(LOG_TAG, "start: Verification interval is " + TimeUnit.MILLISECONDS.toSeconds(_verificationInterval) + " seconds"); 154 if (_timer == null) { 155 _timer = new Timer(); 156 } 157 _timer.scheduleAtFixedRate(new TimerTask() { 158 @SuppressWarnings("unchecked") // Generic reference is irrelevant. 159 @Override 160 public void run() { 161 if (!Thread.currentThread().isInterrupted()) { 162 GigyaLogger.debug(LOG_TAG, "dispatching verifyLogin request " + new Date().toString()); 163 _lastRequestTimestamp = System.currentTimeMillis(); 164 final Map<String, Object> params = new HashMap<>(); 165 params.put("include", "identities-all,loginIDs,profile,email,data"); 166 final GigyaApiRequest request = _requestFactory.create( 167 GigyaDefinitions.API.API_VERIFY_LOGIN, 168 params, 169 RestAdapter.HttpMethod.POST); 170 _apiService.send(request, false, new ApiService.IApiServiceResponse() { 171 @Override 172 public void onApiSuccess(GigyaApiResponse response) { 173 if (response.getErrorCode() == 0) { 174 GigyaLogger.debug(LOG_TAG, "verifyLogin success"); 175 } else { 176 evaluateVerifyLoginError(GigyaError.fromResponse(response)); 177 } 178 } 179 180 @Override 181 public void onApiError(GigyaError gigyaError) { 182 evaluateVerifyLoginError(gigyaError); 183 } 184 }); 185 } 186 } 187 }, getInitialDelay(), _verificationInterval); 188 } 189 190 @Override 191 public void stop() { 192 GigyaLogger.debug(LOG_TAG, "stop: "); 193 if (_timer != null) { 194 _timer.cancel(); 195 _timer.purge(); 196 _timer = null; 197 } 198 System.gc(); 199 } 200 201 private void restart() { 202 stop(); 203 start(); 204 } 205 206 /** 207 * get the initial timer delay. 208 * 209 * @return Initial timer task delay in milliseconds. 210 */ 211 @Override 212 public long getInitialDelay() { 213 if (_lastRequestTimestamp == 0) { 214 return _verificationInterval; 215 } 216 final long interval = _verificationInterval; 217 final long delta = System.currentTimeMillis() - _lastRequestTimestamp; 218 final long initialDelay = delta > interval ? 0 : interval - delta; 219 GigyaLogger.debug(LOG_TAG, "getInitialDelay: " + TimeUnit.MILLISECONDS.toSeconds(initialDelay) + " seconds"); 220 return initialDelay; 221 } 222 223 /** 224 * Evaluate notifyLogin endpoint error. 225 * Will ignore network error. 226 * 227 * @param error GigyaError received. 228 */ 229 private void evaluateVerifyLoginError(GigyaError error) { 230 final int errorCode = error.getErrorCode(); 231 if (errorCode == GigyaError.Codes.ERROR_NETWORK) { 232 return; 233 } 234 GigyaLogger.error(LOG_TAG, "evaluateVerifyLoginError: error = " + errorCode + " session invalid -> invalidate & notify"); 235 notifyInvalidSession(); 236 } 237 238 /** 239 * Session no longer valid. 240 * 1. Clear saved session & invalidate cached account. 241 * 2. Broadcast a local event to notify that the session is invalid. 242 */ 243 private void notifyInvalidSession() { 244 GigyaLogger.debug(LOG_TAG, "notifyInvalidSession: Invalidating session and cached account. Trigger local broadcast"); 245 // Clear current session & cached account. 246 _sessionService.clear(true); 247 _accountService.invalidateAccount(); 248 // Send "session invalid" local broadcast & flush the timer. 249 LocalBroadcastManager.getInstance(_context) 250 .sendBroadcast(new Intent(GigyaDefinitions.Broadcasts.INTENT_ACTION_SESSION_INVALID)); 251 stop(); 252 } 253}