From 5bdf03a849afa370cb1f3690601c8163bbebee6c Mon Sep 17 00:00:00 2001 From: Hamza Israr Date: Fri, 3 Nov 2023 22:02:57 +0500 Subject: [PATCH 1/2] feat: Add Token Refresh Implementation to Network Layer It applies token authentication to the network requests before a network call is made. The expiry duration is saved and it is applied before a network request is queued. --- .../OauthRefreshTokenAuthenticator.kt | 122 +++++++++++++++--- .../app/data/storage/PreferencesManager.kt | 35 ++++- .../org/openedx/app/di/NetworkingModule.kt | 3 +- .../openedx/auth/data/model/AuthResponse.kt | 1 - .../auth/data/repository/AuthRepository.kt | 5 +- .../core/data/storage/CorePreferences.kt | 3 +- .../java/org/openedx/core/utils/TimeUtils.kt | 6 + 7 files changed, 144 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/org/openedx/app/data/networking/OauthRefreshTokenAuthenticator.kt b/app/src/main/java/org/openedx/app/data/networking/OauthRefreshTokenAuthenticator.kt index d2ecfa333..3b7521dd3 100644 --- a/app/src/main/java/org/openedx/app/data/networking/OauthRefreshTokenAuthenticator.kt +++ b/app/src/main/java/org/openedx/app/data/networking/OauthRefreshTokenAuthenticator.kt @@ -4,6 +4,8 @@ import android.util.Log import com.google.gson.Gson import kotlinx.coroutines.runBlocking import okhttp3.* +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.logging.HttpLoggingInterceptor import org.json.JSONException import org.json.JSONObject @@ -16,6 +18,7 @@ import org.openedx.core.ApiConstants.TOKEN_TYPE_JWT import org.openedx.core.BuildConfig import org.openedx.core.BuildConfig.ACCESS_TOKEN_TYPE import org.openedx.core.data.storage.CorePreferences +import org.openedx.core.utils.TimeUtils import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import java.io.IOException @@ -24,9 +27,20 @@ import java.util.concurrent.TimeUnit class OauthRefreshTokenAuthenticator( private val preferencesManager: CorePreferences, private val appNotifier: AppNotifier, -) : Authenticator { +) : Authenticator, Interceptor { private val authApi: AuthApi + private var lastTokenRefreshRequestTime = 0L + + override fun intercept(chain: Interceptor.Chain): Response { + if (isTokenExpired()) { + val response = createUnauthorizedResponse(chain) + val request = authenticate(chain.connection()?.route(), response) + + return request?.let { chain.proceed(it) } ?: chain.proceed(chain.request()) + } + return chain.proceed(chain.request()) + } init { val okHttpClient = OkHttpClient.Builder().apply { @@ -44,6 +58,7 @@ class OauthRefreshTokenAuthenticator( .create(AuthApi::class.java) } + @Synchronized override fun authenticate(route: Route?, response: Response): Request? { val accessToken = preferencesManager.accessToken val refreshToken = preferencesManager.refreshToken @@ -112,26 +127,43 @@ class OauthRefreshTokenAuthenticator( return null } + private fun isTokenExpired(): Boolean { + val timeInSeconds = TimeUtils.getCurrentTimeInSeconds() + REFRESH_TOKEN_EXPIRY_THRESHOLD + return timeInSeconds >= preferencesManager.accessTokenExpiresAt + } + + private fun canRequestTokenRefresh(): Boolean { + return TimeUtils.getCurrentTimeInSeconds() - lastTokenRefreshRequestTime > + REFRESH_TOKEN_INTERVAL_MINIMUM + } + @Throws(IOException::class) private fun refreshAccessToken(refreshToken: String): AuthResponse? { - val response = authApi.refreshAccessToken( - ApiConstants.TOKEN_TYPE_REFRESH, - BuildConfig.CLIENT_ID, - refreshToken, - ACCESS_TOKEN_TYPE - ).execute() - val authResponse = response.body() - if (response.isSuccessful && authResponse != null) { - val newAccessToken = authResponse.accessToken ?: "" - val newRefreshToken = authResponse.refreshToken ?: "" - - if (newAccessToken.isNotEmpty() && newRefreshToken.isNotEmpty()) { - preferencesManager.accessToken = newAccessToken - preferencesManager.refreshToken = newRefreshToken + var authResponse: AuthResponse? = null + if (canRequestTokenRefresh()) { + val response = authApi.refreshAccessToken( + ApiConstants.TOKEN_TYPE_REFRESH, + BuildConfig.CLIENT_ID, + refreshToken, + ACCESS_TOKEN_TYPE + ).execute() + authResponse = response.body() + if (response.isSuccessful && authResponse != null) { + val newAccessToken = authResponse.accessToken ?: "" + val newRefreshToken = authResponse.refreshToken ?: "" + val newExpireTime = authResponse.expiresIn ?: 0L + + if (newAccessToken.isNotEmpty() && newRefreshToken.isNotEmpty() && newExpireTime > 0L) { + preferencesManager.accessToken = newAccessToken + preferencesManager.refreshToken = newRefreshToken + preferencesManager.accessTokenExpiresAt = + newExpireTime + TimeUtils.getCurrentTimeInSeconds() + lastTokenRefreshRequestTime = TimeUtils.getCurrentTimeInSeconds() + } + } else if (response.code() == 400) { + //another refresh already in progress + Thread.sleep(1500) } - } else if (response.code() == 400) { - //another refresh already in progress - Thread.sleep(1500) } return authResponse @@ -144,7 +176,8 @@ class OauthRefreshTokenAuthenticator( return jsonObj.getString(FIELD_ERROR_CODE) } else { return if (TOKEN_TYPE_JWT.equals(ACCESS_TOKEN_TYPE, ignoreCase = true)) { - val errorType = if (jsonObj.has(FIELD_DETAIL)) FIELD_DETAIL else FIELD_DEVELOPER_MESSAGE + val errorType = + if (jsonObj.has(FIELD_DETAIL)) FIELD_DETAIL else FIELD_DEVELOPER_MESSAGE jsonObj.getString(errorType) } else { val errorCode = jsonObj @@ -163,6 +196,41 @@ class OauthRefreshTokenAuthenticator( } } + /** + * [createUnauthorizedResponse] creates an unauthorized okhttp response with the initial chain + * request for [authenticate] method of [OauthRefreshTokenAuthenticator]. The response is + * specially designed to trigger the 'Token Expired' case of the [authenticate] method so that + * it can handle the refresh logic of the access token accordingly. + * + * @param chain Chain request for authentication + * @return Custom unauthorized response builder with initial request + */ + private fun createUnauthorizedResponse(chain: Interceptor.Chain) = Response.Builder() + .code(401) + .request(chain.request()) + .protocol(Protocol.HTTP_1_1) + .message("Unauthorized") + .headers(chain.request().headers) + .body(getResponseBody()) + .build() + + /** + * [getResponseBody] generates an error response body based on access token type because both + * Bearer and JWT have their own sets of errors. + * + * @return ResponseBody based on access token type + */ + private fun getResponseBody(): ResponseBody { + val tokenType = ACCESS_TOKEN_TYPE + val jsonObject = if (TOKEN_TYPE_JWT.equals(tokenType, ignoreCase = true)) { + JSONObject().put("detail", JWT_TOKEN_EXPIRED) + } else { + JSONObject().put("error_code", TOKEN_EXPIRED_ERROR_MESSAGE) + } + + return jsonObject.toString().toResponseBody("application/json".toMediaType()) + } + companion object { private const val HEADER_AUTHORIZATION = "Authorization" @@ -177,5 +245,19 @@ class OauthRefreshTokenAuthenticator( private const val FIELD_ERROR_CODE = "error_code" private const val FIELD_DETAIL = "detail" private const val FIELD_DEVELOPER_MESSAGE = "developer_message" + + /** + * [REFRESH_TOKEN_EXPIRY_THRESHOLD] behave as a buffer time to be used in the expiry + * verification method of the access token to ensure that the token doesn't expire during + * an active session. + */ + private const val REFRESH_TOKEN_EXPIRY_THRESHOLD = 60 + + /** + * [REFRESH_TOKEN_INTERVAL_MINIMUM] behave as a buffer time for refresh token network + * requests. It prevents multiple calls to refresh network requests in case of an + * unauthorized access token during async requests. + */ + private const val REFRESH_TOKEN_INTERVAL_MINIMUM = 60 } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/openedx/app/data/storage/PreferencesManager.kt b/app/src/main/java/org/openedx/app/data/storage/PreferencesManager.kt index b663690b9..bd7eb17e5 100644 --- a/app/src/main/java/org/openedx/app/data/storage/PreferencesManager.kt +++ b/app/src/main/java/org/openedx/app/data/storage/PreferencesManager.kt @@ -3,26 +3,36 @@ package org.openedx.app.data.storage import android.content.Context import com.google.gson.Gson import org.openedx.app.BuildConfig -import org.openedx.core.data.storage.CorePreferences -import org.openedx.profile.data.model.Account import org.openedx.core.data.model.User +import org.openedx.core.data.storage.CorePreferences import org.openedx.core.data.storage.InAppReviewPreferences import org.openedx.core.domain.model.VideoSettings +import org.openedx.profile.data.model.Account import org.openedx.profile.data.storage.ProfilePreferences import org.openedx.whatsnew.data.storage.WhatsNewPreferences -class PreferencesManager(context: Context) : CorePreferences, ProfilePreferences, WhatsNewPreferences, - InAppReviewPreferences { +class PreferencesManager(context: Context) : CorePreferences, ProfilePreferences, + WhatsNewPreferences, InAppReviewPreferences { - private val sharedPreferences = context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE) + private val sharedPreferences = + context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE) private fun saveString(key: String, value: String) { sharedPreferences.edit().apply { putString(key, value) }.apply() } + private fun getString(key: String): String = sharedPreferences.getString(key, "") ?: "" + private fun saveLong(key: String, value: Long) { + sharedPreferences.edit().apply { + putLong(key, value) + }.apply() + } + + private fun getLong(key: String): Long = sharedPreferences.getLong(key, 0L) + private fun saveBoolean(key: String, value: Boolean) { sharedPreferences.edit().apply { putBoolean(key, value) @@ -36,6 +46,7 @@ class PreferencesManager(context: Context) : CorePreferences, ProfilePreferences remove(ACCESS_TOKEN) remove(REFRESH_TOKEN) remove(USER) + remove(EXPIRES_IN) }.apply() } @@ -51,6 +62,12 @@ class PreferencesManager(context: Context) : CorePreferences, ProfilePreferences } get() = getString(REFRESH_TOKEN) + override var accessTokenExpiresAt: Long + set(value) { + saveLong(EXPIRES_IN, value) + } + get() = getLong(EXPIRES_IN) + override var user: User? set(value) { val userJson = Gson().toJson(value) @@ -95,7 +112,10 @@ class PreferencesManager(context: Context) : CorePreferences, ProfilePreferences } get() { val versionNameString = getString(LAST_REVIEW_VERSION) - return Gson().fromJson(versionNameString, InAppReviewPreferences.VersionName::class.java) + return Gson().fromJson( + versionNameString, + InAppReviewPreferences.VersionName::class.java + ) ?: InAppReviewPreferences.VersionName.default } @@ -109,6 +129,7 @@ class PreferencesManager(context: Context) : CorePreferences, ProfilePreferences companion object { private const val ACCESS_TOKEN = "access_token" private const val REFRESH_TOKEN = "refresh_token" + private const val EXPIRES_IN = "expires_in" private const val USER = "user" private const val ACCOUNT = "account" private const val VIDEO_SETTINGS = "video_settings" @@ -116,4 +137,4 @@ class PreferencesManager(context: Context) : CorePreferences, ProfilePreferences private const val LAST_REVIEW_VERSION = "last_review_version" private const val APP_WAS_POSITIVE_RATED = "app_was_positive_rated" } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/openedx/app/di/NetworkingModule.kt b/app/src/main/java/org/openedx/app/di/NetworkingModule.kt index 5d987e568..3b09c6fa7 100644 --- a/app/src/main/java/org/openedx/app/di/NetworkingModule.kt +++ b/app/src/main/java/org/openedx/app/di/NetworkingModule.kt @@ -31,6 +31,7 @@ val networkingModule = module { } addInterceptor(HandleErrorInterceptor(get())) addInterceptor(AppUpgradeInterceptor(get())) + addInterceptor(get()) authenticator(get()) }.build() } @@ -53,4 +54,4 @@ val networkingModule = module { inline fun provideApi(retrofit: Retrofit): T { return retrofit.create(T::class.java) -} \ No newline at end of file +} diff --git a/auth/src/main/java/org/openedx/auth/data/model/AuthResponse.kt b/auth/src/main/java/org/openedx/auth/data/model/AuthResponse.kt index 6890dcfce..622ddb928 100644 --- a/auth/src/main/java/org/openedx/auth/data/model/AuthResponse.kt +++ b/auth/src/main/java/org/openedx/auth/data/model/AuthResponse.kt @@ -16,4 +16,3 @@ data class AuthResponse( @SerializedName("refresh_token") var refreshToken: String?, ) - diff --git a/auth/src/main/java/org/openedx/auth/data/repository/AuthRepository.kt b/auth/src/main/java/org/openedx/auth/data/repository/AuthRepository.kt index 21f93176e..b4a1d8749 100644 --- a/auth/src/main/java/org/openedx/auth/data/repository/AuthRepository.kt +++ b/auth/src/main/java/org/openedx/auth/data/repository/AuthRepository.kt @@ -6,6 +6,7 @@ import org.openedx.core.ApiConstants import org.openedx.core.data.storage.CorePreferences import org.openedx.core.domain.model.RegistrationField import org.openedx.core.system.EdxError +import org.openedx.core.utils.TimeUtils class AuthRepository( private val api: AuthApi, @@ -28,6 +29,8 @@ class AuthRepository( } preferencesManager.accessToken = authResponse.accessToken ?: "" preferencesManager.refreshToken = authResponse.refreshToken ?: "" + preferencesManager.accessTokenExpiresAt = + (authResponse.expiresIn ?: 0L) + TimeUtils.getCurrentTimeInSeconds() val user = api.getProfile() preferencesManager.user = user } @@ -47,4 +50,4 @@ class AuthRepository( suspend fun passwordReset(email: String): Boolean { return api.passwordReset(email).success } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/openedx/core/data/storage/CorePreferences.kt b/core/src/main/java/org/openedx/core/data/storage/CorePreferences.kt index bfeb61c52..11f21c661 100644 --- a/core/src/main/java/org/openedx/core/data/storage/CorePreferences.kt +++ b/core/src/main/java/org/openedx/core/data/storage/CorePreferences.kt @@ -6,8 +6,9 @@ import org.openedx.core.domain.model.VideoSettings interface CorePreferences { var accessToken: String var refreshToken: String + var accessTokenExpiresAt: Long var user: User? var videoSettings: VideoSettings fun clear() -} \ No newline at end of file +} diff --git a/core/src/main/java/org/openedx/core/utils/TimeUtils.kt b/core/src/main/java/org/openedx/core/utils/TimeUtils.kt index b70d9c7b7..c60a2ad60 100644 --- a/core/src/main/java/org/openedx/core/utils/TimeUtils.kt +++ b/core/src/main/java/org/openedx/core/utils/TimeUtils.kt @@ -9,8 +9,10 @@ import org.openedx.core.system.ResourceManager import java.text.ParseException import java.text.ParsePosition import java.text.SimpleDateFormat +import java.util.Calendar import java.util.Date import java.util.Locale +import java.util.concurrent.TimeUnit object TimeUtils { @@ -22,6 +24,10 @@ object TimeUtils { private const val SEVEN_DAYS_IN_MILLIS = 604800000L + fun getCurrentTimeInSeconds(): Long { + return TimeUnit.MILLISECONDS.toSeconds(Calendar.getInstance().timeInMillis) + } + fun iso8601ToDate(text: String): Date? { return try { val parsePosition = ParsePosition(0) From 5c6227bdc0365019645af78715479dcb0db84252 Mon Sep 17 00:00:00 2001 From: Hamza Israr Date: Wed, 22 Nov 2023 03:54:32 +0500 Subject: [PATCH 2/2] refactor: Use milliseconds for expiry time --- .../OauthRefreshTokenAuthenticator.kt | 23 +++++++++---------- .../openedx/auth/data/model/AuthResponse.kt | 14 ++++++++++- .../auth/data/repository/AuthRepository.kt | 9 ++++---- .../openedx/auth/domain/model/AuthResponse.kt | 19 +++++++++++++++ .../java/org/openedx/core/utils/TimeUtils.kt | 5 ++-- 5 files changed, 49 insertions(+), 21 deletions(-) create mode 100644 auth/src/main/java/org/openedx/auth/domain/model/AuthResponse.kt diff --git a/app/src/main/java/org/openedx/app/data/networking/OauthRefreshTokenAuthenticator.kt b/app/src/main/java/org/openedx/app/data/networking/OauthRefreshTokenAuthenticator.kt index 3b7521dd3..8a9601995 100644 --- a/app/src/main/java/org/openedx/app/data/networking/OauthRefreshTokenAuthenticator.kt +++ b/app/src/main/java/org/openedx/app/data/networking/OauthRefreshTokenAuthenticator.kt @@ -12,7 +12,7 @@ import org.json.JSONObject import org.openedx.app.system.notifier.AppNotifier import org.openedx.app.system.notifier.LogoutEvent import org.openedx.auth.data.api.AuthApi -import org.openedx.auth.data.model.AuthResponse +import org.openedx.auth.domain.model.AuthResponse import org.openedx.core.ApiConstants import org.openedx.core.ApiConstants.TOKEN_TYPE_JWT import org.openedx.core.BuildConfig @@ -128,12 +128,12 @@ class OauthRefreshTokenAuthenticator( } private fun isTokenExpired(): Boolean { - val timeInSeconds = TimeUtils.getCurrentTimeInSeconds() + REFRESH_TOKEN_EXPIRY_THRESHOLD - return timeInSeconds >= preferencesManager.accessTokenExpiresAt + val time = TimeUtils.getCurrentTime() + REFRESH_TOKEN_EXPIRY_THRESHOLD + return time >= preferencesManager.accessTokenExpiresAt } private fun canRequestTokenRefresh(): Boolean { - return TimeUtils.getCurrentTimeInSeconds() - lastTokenRefreshRequestTime > + return TimeUtils.getCurrentTime() - lastTokenRefreshRequestTime > REFRESH_TOKEN_INTERVAL_MINIMUM } @@ -147,18 +147,17 @@ class OauthRefreshTokenAuthenticator( refreshToken, ACCESS_TOKEN_TYPE ).execute() - authResponse = response.body() + authResponse = response.body()?.mapToDomain() if (response.isSuccessful && authResponse != null) { val newAccessToken = authResponse.accessToken ?: "" val newRefreshToken = authResponse.refreshToken ?: "" - val newExpireTime = authResponse.expiresIn ?: 0L + val newExpireTime = authResponse.getTokenExpiryTime() - if (newAccessToken.isNotEmpty() && newRefreshToken.isNotEmpty() && newExpireTime > 0L) { + if (newAccessToken.isNotEmpty() && newRefreshToken.isNotEmpty()) { preferencesManager.accessToken = newAccessToken preferencesManager.refreshToken = newRefreshToken - preferencesManager.accessTokenExpiresAt = - newExpireTime + TimeUtils.getCurrentTimeInSeconds() - lastTokenRefreshRequestTime = TimeUtils.getCurrentTimeInSeconds() + preferencesManager.accessTokenExpiresAt = newExpireTime + lastTokenRefreshRequestTime = TimeUtils.getCurrentTime() } } else if (response.code() == 400) { //another refresh already in progress @@ -251,13 +250,13 @@ class OauthRefreshTokenAuthenticator( * verification method of the access token to ensure that the token doesn't expire during * an active session. */ - private const val REFRESH_TOKEN_EXPIRY_THRESHOLD = 60 + private const val REFRESH_TOKEN_EXPIRY_THRESHOLD = 60 * 1000 /** * [REFRESH_TOKEN_INTERVAL_MINIMUM] behave as a buffer time for refresh token network * requests. It prevents multiple calls to refresh network requests in case of an * unauthorized access token during async requests. */ - private const val REFRESH_TOKEN_INTERVAL_MINIMUM = 60 + private const val REFRESH_TOKEN_INTERVAL_MINIMUM = 60 * 1000 } } diff --git a/auth/src/main/java/org/openedx/auth/data/model/AuthResponse.kt b/auth/src/main/java/org/openedx/auth/data/model/AuthResponse.kt index 622ddb928..64c5cf27e 100644 --- a/auth/src/main/java/org/openedx/auth/data/model/AuthResponse.kt +++ b/auth/src/main/java/org/openedx/auth/data/model/AuthResponse.kt @@ -1,6 +1,7 @@ package org.openedx.auth.data.model import com.google.gson.annotations.SerializedName +import org.openedx.auth.domain.model.AuthResponse data class AuthResponse( @SerializedName("access_token") @@ -15,4 +16,15 @@ data class AuthResponse( var error: String?, @SerializedName("refresh_token") var refreshToken: String?, -) +) { + fun mapToDomain(): AuthResponse { + return AuthResponse( + accessToken = accessToken, + tokenType = tokenType, + expiresIn = expiresIn?.times(1000), + scope = scope, + error = error, + refreshToken = refreshToken, + ) + } +} diff --git a/auth/src/main/java/org/openedx/auth/data/repository/AuthRepository.kt b/auth/src/main/java/org/openedx/auth/data/repository/AuthRepository.kt index b4a1d8749..9a96674dc 100644 --- a/auth/src/main/java/org/openedx/auth/data/repository/AuthRepository.kt +++ b/auth/src/main/java/org/openedx/auth/data/repository/AuthRepository.kt @@ -2,11 +2,11 @@ package org.openedx.auth.data.repository import org.openedx.auth.data.api.AuthApi import org.openedx.auth.data.model.ValidationFields +import org.openedx.auth.domain.model.AuthResponse import org.openedx.core.ApiConstants import org.openedx.core.data.storage.CorePreferences import org.openedx.core.domain.model.RegistrationField import org.openedx.core.system.EdxError -import org.openedx.core.utils.TimeUtils class AuthRepository( private val api: AuthApi, @@ -17,20 +17,19 @@ class AuthRepository( username: String, password: String, ) { - val authResponse = api.getAccessToken( + val authResponse: AuthResponse = api.getAccessToken( ApiConstants.GRANT_TYPE_PASSWORD, org.openedx.core.BuildConfig.CLIENT_ID, username, password, org.openedx.core.BuildConfig.ACCESS_TOKEN_TYPE - ) + ).mapToDomain() if (authResponse.error != null) { throw EdxError.UnknownException(authResponse.error!!) } preferencesManager.accessToken = authResponse.accessToken ?: "" preferencesManager.refreshToken = authResponse.refreshToken ?: "" - preferencesManager.accessTokenExpiresAt = - (authResponse.expiresIn ?: 0L) + TimeUtils.getCurrentTimeInSeconds() + preferencesManager.accessTokenExpiresAt = authResponse.getTokenExpiryTime() val user = api.getProfile() preferencesManager.user = user } diff --git a/auth/src/main/java/org/openedx/auth/domain/model/AuthResponse.kt b/auth/src/main/java/org/openedx/auth/domain/model/AuthResponse.kt new file mode 100644 index 000000000..47c5a0cf4 --- /dev/null +++ b/auth/src/main/java/org/openedx/auth/domain/model/AuthResponse.kt @@ -0,0 +1,19 @@ +package org.openedx.auth.domain.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import org.openedx.core.utils.TimeUtils + +@Parcelize +data class AuthResponse( + var accessToken: String?, + var tokenType: String?, + var expiresIn: Long?, + var scope: String?, + var error: String?, + var refreshToken: String?, +) : Parcelable { + fun getTokenExpiryTime(): Long { + return (expiresIn ?: 0L) + TimeUtils.getCurrentTime() + } +} diff --git a/core/src/main/java/org/openedx/core/utils/TimeUtils.kt b/core/src/main/java/org/openedx/core/utils/TimeUtils.kt index c60a2ad60..99cf7ffbc 100644 --- a/core/src/main/java/org/openedx/core/utils/TimeUtils.kt +++ b/core/src/main/java/org/openedx/core/utils/TimeUtils.kt @@ -12,7 +12,6 @@ import java.text.SimpleDateFormat import java.util.Calendar import java.util.Date import java.util.Locale -import java.util.concurrent.TimeUnit object TimeUtils { @@ -24,8 +23,8 @@ object TimeUtils { private const val SEVEN_DAYS_IN_MILLIS = 604800000L - fun getCurrentTimeInSeconds(): Long { - return TimeUnit.MILLISECONDS.toSeconds(Calendar.getInstance().timeInMillis) + fun getCurrentTime(): Long { + return Calendar.getInstance().timeInMillis } fun iso8601ToDate(text: String): Date? {