202 lines
6.8 KiB
Kotlin
202 lines
6.8 KiB
Kotlin
package de.mm20.launcher2.msservices
|
|
|
|
import android.app.Activity
|
|
import android.content.Context
|
|
import android.util.Log
|
|
import androidx.core.content.edit
|
|
import com.azure.core.credential.AccessToken
|
|
import com.azure.core.credential.TokenCredential
|
|
import com.microsoft.graph.authentication.TokenCredentialAuthProvider
|
|
import com.microsoft.graph.core.ClientException
|
|
import com.microsoft.graph.http.GraphServiceException
|
|
import com.microsoft.graph.models.DriveSearchParameterSet
|
|
import com.microsoft.graph.requests.GraphServiceClient
|
|
import com.microsoft.identity.client.AuthenticationCallback
|
|
import com.microsoft.identity.client.IAuthenticationResult
|
|
import com.microsoft.identity.client.ISingleAccountPublicClientApplication
|
|
import com.microsoft.identity.client.PublicClientApplication
|
|
import com.microsoft.identity.client.exception.MsalClientException
|
|
import com.microsoft.identity.client.exception.MsalException
|
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.withContext
|
|
import okhttp3.Request
|
|
import reactor.core.publisher.Mono
|
|
import java.net.URLEncoder
|
|
import kotlin.coroutines.resume
|
|
import kotlin.coroutines.suspendCoroutine
|
|
|
|
class MicrosoftGraphApiHelper(val context: Context) {
|
|
|
|
private var accessToken: String? = null
|
|
private val client: GraphServiceClient<Request> = GraphServiceClient
|
|
.builder()
|
|
.authenticationProvider(TokenCredentialAuthProvider {
|
|
Mono.just(AccessToken(accessToken, null))
|
|
})
|
|
.buildClient()
|
|
|
|
private var clientApplication: ISingleAccountPublicClientApplication? = null
|
|
|
|
private suspend fun getClientApplication(): ISingleAccountPublicClientApplication? {
|
|
val resId = getConfigResId()
|
|
if (resId == 0) return null
|
|
if (clientApplication == null) {
|
|
clientApplication = withContext(Dispatchers.IO) {
|
|
PublicClientApplication.createSingleAccountPublicClientApplication(
|
|
context.applicationContext,
|
|
resId
|
|
)
|
|
}
|
|
}
|
|
return clientApplication!!
|
|
}
|
|
|
|
private suspend fun acquireAccessToken(): Boolean {
|
|
val result = withContext(Dispatchers.IO) {
|
|
try {
|
|
val application = getClientApplication() ?: return@withContext null
|
|
val authority = application.configuration.defaultAuthority.authorityURL.toString()
|
|
application.acquireTokenSilent(SCOPES, authority)
|
|
} catch (e: MsalException) {
|
|
CrashReporter.logException(e)
|
|
logout()
|
|
null
|
|
} catch (e: ClientException) {
|
|
CrashReporter.logException(e)
|
|
null
|
|
}
|
|
}
|
|
accessToken = result?.accessToken
|
|
return result != null
|
|
}
|
|
|
|
suspend fun login(context: Activity) {
|
|
val clientApplication = getClientApplication() ?: return
|
|
suspendCoroutine<IAuthenticationResult?> {
|
|
clientApplication.signIn(context, "", SCOPES, object : AuthenticationCallback {
|
|
override fun onSuccess(authenticationResult: IAuthenticationResult?) {
|
|
accessToken = authenticationResult?.accessToken
|
|
it.resume(authenticationResult)
|
|
}
|
|
|
|
override fun onCancel() {
|
|
it.resume(null)
|
|
}
|
|
|
|
override fun onError(exception: MsalException?) {
|
|
if (exception != null) Log.e("MM20", exception.stackTraceToString())
|
|
it.resume(null)
|
|
}
|
|
|
|
})
|
|
}
|
|
loadAccountName()
|
|
}
|
|
|
|
suspend fun logout() {
|
|
accessToken = null
|
|
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit {
|
|
putString(PREF_ACCOUNT_NAME, null)
|
|
}
|
|
withContext(Dispatchers.IO) {
|
|
try {
|
|
getClientApplication()?.signOut()
|
|
} catch (e: MsalClientException) {
|
|
CrashReporter.logException(e)
|
|
}
|
|
}
|
|
}
|
|
|
|
suspend fun getUser(): MsUser? {
|
|
val name = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getString(
|
|
PREF_ACCOUNT_NAME,
|
|
null
|
|
) ?: loadAccountName()
|
|
|
|
|
|
return name?.let {
|
|
MsUser(name = it)
|
|
}
|
|
}
|
|
|
|
private suspend fun loadAccountName(): String? {
|
|
if (!isLoggedIn()) return null
|
|
if (!acquireAccessToken()) return null
|
|
return withContext(Dispatchers.IO) {
|
|
try {
|
|
val user = client.me().buildRequest().get() ?: return@withContext null
|
|
val name = user.displayName ?: user.mail ?: "Microsoft User"
|
|
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit {
|
|
putString(PREF_ACCOUNT_NAME, name)
|
|
}
|
|
return@withContext name
|
|
} catch (e: GraphServiceException) {
|
|
CrashReporter.logException(e)
|
|
logout()
|
|
} catch (e: ClientException) {
|
|
CrashReporter.logException(e)
|
|
}
|
|
null
|
|
}
|
|
}
|
|
|
|
suspend fun isLoggedIn(): Boolean {
|
|
return withContext(Dispatchers.IO) {
|
|
getClientApplication()?.currentAccount?.currentAccount != null
|
|
}
|
|
}
|
|
|
|
|
|
suspend fun queryOneDriveFiles(query: String): List<DriveItem>? {
|
|
if (!acquireAccessToken()) return null
|
|
return try {
|
|
withContext(Dispatchers.IO) {
|
|
client.me().drive().search(
|
|
DriveSearchParameterSet.newBuilder()
|
|
.withQ(query)
|
|
.build()
|
|
)
|
|
.buildRequest()
|
|
.select("id,name,file,size,video,image,webUrl,shared,createdBy")
|
|
.top(10)
|
|
.get()
|
|
?.currentPage
|
|
?.mapNotNull { DriveItem.fromApiDriveItem(it) }
|
|
}
|
|
} catch (e: GraphServiceException) {
|
|
CrashReporter.logException(e)
|
|
null
|
|
} catch (e: ClientException) {
|
|
CrashReporter.logException(e)
|
|
null
|
|
}
|
|
}
|
|
|
|
fun isAvailable(): Boolean {
|
|
return getConfigResId() != 0
|
|
}
|
|
|
|
private fun getConfigResId(): Int {
|
|
return context.resources.getIdentifier("msal_auth_config", "raw", context.packageName)
|
|
}
|
|
|
|
companion object {
|
|
private lateinit var instance: MicrosoftGraphApiHelper
|
|
|
|
fun getInstance(context: Context): MicrosoftGraphApiHelper {
|
|
if (!Companion::instance.isInitialized) instance =
|
|
MicrosoftGraphApiHelper(context.applicationContext)
|
|
return instance
|
|
}
|
|
|
|
private val SCOPES = arrayOf(
|
|
"User.Read",
|
|
"Files.Read.All"
|
|
)
|
|
|
|
const val PREFS = "ms-account"
|
|
const val PREF_ACCOUNT_NAME = "name"
|
|
}
|
|
|
|
} |