Skip to content

Commit

Permalink
Merge pull request #80 from applivery/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
imablanco authored Dec 11, 2024
2 parents 9c7f6a2 + f0ef046 commit 7cb3090
Show file tree
Hide file tree
Showing 19 changed files with 419 additions and 80 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ jobs:
uses: actions/setup-java@v1
with:
java-version: 11
- name: Detekt
run: ./gradlew detekt
- name: Build with Gradle
run: ./gradlew build
run: ./gradlew assemble
38 changes: 35 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,41 @@ class AppliveryApplication : Application() {
```

This method is intended to initialize the Applivery SDK. The only thing you have to take care about is that this call **MUST** be performed in App's `onCreate()` Method.

**IMPORTANT: Don't init Applivery on `release` builds**


**IMPORTANT: Don't init Applivery on `release` builds**

#### Auth configuration

In order to configure SDK authentication flow, you have to define a URL redirect schema unique to your application so we can callback the app when authentication flow completes.

First, init the SDK using the init method that accepts a `redirectScheme` parameter:

```kotlin
class AppliveryApplication : Application() {

override fun onCreate() {
super.onCreate()

if (BuildConfig.BUILD_TYPE != "release") {
Applivery.init(this, BuildConfig.APPLIVERY_APP_TOKEN, "tenant" /*optional*/, "redirectScheme")
}
}
}
```

Next, define a new Manifest placeholder inside you app's build gradle file with the same scheme used in the init method:

```groovy
manifestPlaceholders = [appliveryAuthRedirectScheme: "${customScheme}"]
```
Scheme must be a custom scheme in order to ensure ir remains unique (typically your applicationId).

If you are not using Applivery authentication just leave the field empty and use a constructor that does not accept this parameter.

```groovy
manifestPlaceholders = [appliveryAuthRedirectScheme: ""]
```

### Step 2
Once initialized the SDK and **once your App is stable in the Home Screen** you have to call proactivelly the following method in order to check for new updates:
```kotlin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ object AppliveryDataManager {
var appData: AppData? = null
var appToken: String? = null
var tenant: String? = null
var redirectScheme: String? = null
}
4 changes: 3 additions & 1 deletion applvsdklib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ dependencies {
implementation "com.squareup.retrofit2:retrofit:${retrofitVersion}"
implementation "com.squareup.retrofit2:converter-gson:${retrofitVersion}"
implementation "com.karumi:dexter:${dexterVersion}"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:${lifecycleScopeVersion}"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:${lifecycleVersion}"
implementation "androidx.lifecycle:lifecycle-common-java8:${lifecycleVersion}"
implementation "androidx.browser:browser:${browserVersion}"

// Test dependencies
testImplementation "junit:junit:${junitVersion}"
Expand Down
20 changes: 19 additions & 1 deletion applvsdklib/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,25 @@

<application android:supportsRtl="true">

<activity android:name=".ui.views.login.LoginActivity" />
<activity
android:name=".ui.views.login.LoginActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
android:exported="false"
android:launchMode="singleTask"
android:theme="@style/Theme.AppCompat.Translucent.NoTitleBar" />

<activity
android:name=".ui.views.login.RedirectUriReceiverActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="${appliveryAuthRedirectScheme}" />
</intent-filter>
</activity>

<provider
android:name=".tools.androidimplementations.AppliveryFileProviderLeg"
Expand Down
19 changes: 17 additions & 2 deletions applvsdklib/src/main/java/com/applivery/applvsdklib/Applivery.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class Applivery {
* app settings section
*/
public static void init(@NonNull Application app, @NonNull String appToken) {
AppliverySdk.sdkInitialize(app, appToken, null);
AppliverySdk.sdkInitialize(app, appToken, null, null);
}

/**
Expand All @@ -53,7 +53,22 @@ public static void init(@NonNull Application app, @NonNull String appToken) {
* @param tenant tenant for private Applivery instances
*/
public static void init(@NonNull Application app, @NonNull String appToken, @NonNull String tenant) {
AppliverySdk.sdkInitialize(app, appToken, tenant);
AppliverySdk.sdkInitialize(app, appToken, tenant, null);
}

/**
* Initializes Sdk for the current app and developer. Call this method from your application
* instance when onCreate method is called. Pay attention to description of isPlayStoreRelease
* param.
*
* @param app your app instance, it can't be null.
* @param appToken your app tokenoken. You can find this value at applivery dashboard in your
* app settings section
* @param tenant tenant for private Applivery instances
* @param redirectScheme redirectScheme for SAML authentication
*/
public static void init(@NonNull Application app, @NonNull String appToken, @Nullable String tenant, @Nullable String redirectScheme) {
AppliverySdk.sdkInitialize(app, appToken, tenant, redirectScheme);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.hardware.Sensor;
import android.net.Uri;
import android.util.Log;

import androidx.annotation.NonNull;
Expand Down Expand Up @@ -74,14 +76,14 @@ public class AppliverySdk {
private static Boolean checkForUpdatesBackground = BuildConfig.CHECK_FOR_UPDATES_BACKGROUND;
private static Boolean isUpdating = false;

public static synchronized void sdkInitialize(Application app, String appToken, String tenant) {
init(app, appToken, tenant);
public static synchronized void sdkInitialize(Application app, String appToken, String tenant, String redirectScheme) {
init(app, appToken, tenant, redirectScheme);
}

private static void init(Application app, String appToken, String tenant) {
private static void init(Application app, String appToken, String tenant, String redirectScheme) {
if (!sdkInitialized) {
sdkInitialized = true;
initializeAppliveryConstants(app, appToken, tenant);
initializeAppliveryConstants(app, appToken, tenant, redirectScheme);
app.registerActivityLifecycleCallbacks(new AppliveryLifecycleCallbacks());
registerActivityLifecyleCallbacks(app);
obtainAppConfig(false);
Expand All @@ -108,7 +110,7 @@ public static synchronized Boolean isUpdating() {
return isUpdating;
}

private static void initializeAppliveryConstants(Application app, String appToken, String tenant) {
private static void initializeAppliveryConstants(Application app, String appToken, String tenant, String redirectScheme) {

//region validate some requirements
Context applicationContext = Validate.notNull(app, "Application").getApplicationContext();
Expand All @@ -118,6 +120,15 @@ private static void initializeAppliveryConstants(Application app, String appToke

AppliveryDataManager.INSTANCE.setAppToken(appToken);
AppliveryDataManager.INSTANCE.setTenant(tenant);
AppliveryDataManager.INSTANCE.setRedirectScheme(redirectScheme);

if (redirectScheme != null && !isRedirectSchemeRegistered(applicationContext, redirectScheme)) {
throw new RuntimeException(
"redirectScheme is not handled by any activity in this app! "
+ "Ensure that appliveryAuthRedirectScheme in your build.gradle file "
+ "is correctly configured, or that an appropriate intent filter "
+ "exists in your app manifest.");
}

AppliverySdk.applicationContext = applicationContext;

Expand Down Expand Up @@ -337,6 +348,19 @@ public Unit invoke(ErrorObject errorObject) {
}


private static boolean isRedirectSchemeRegistered(Context context, String scheme) {
// ensure that the redirect URI declared in the configuration is handled by some activity
// in the app, by querying the package manager speculatively
Intent redirectIntent = new Intent();
redirectIntent.setPackage(context.getPackageName());
redirectIntent.setAction(Intent.ACTION_VIEW);
redirectIntent.addCategory(Intent.CATEGORY_BROWSABLE);
redirectIntent.setData(new Uri.Builder().scheme(scheme).build());

return !context.getPackageManager().queryIntentActivities(redirectIntent, 0).isEmpty();
}


public static class Logger {

private static volatile boolean debug = isDebugEnabled;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,12 @@ package com.applivery.applvsdklib.ui.views.login

import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.webkit.CookieManager
import android.webkit.WebChromeClient
import android.webkit.WebSettings
import android.webkit.WebView
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.applivery.applvsdklib.R
import com.applivery.applvsdklib.tools.injection.Injection
import com.applivery.applvsdklib.ui.views.login.customtabs.CustomTabsManager
import kotlinx.coroutines.launch

class LoginActivity : ComponentActivity() {
Expand All @@ -35,51 +31,70 @@ class LoginActivity : ComponentActivity() {
Injection.provideLoginPresenter()
}

private var authorizationStarted = false

private val customTabsManager by lazy { CustomTabsManager(this) }

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.applivery_activity_login)
val webView = findViewById<WebView>(R.id.webView)
val loadingView = findViewById<View>(R.id.loadingView)
with(webView) {
with(settings) {
javaScriptEnabled = true
allowFileAccess = false
allowContentAccess = false
cacheMode = WebSettings.LOAD_NO_CACHE
databaseEnabled = true
domStorageEnabled = true
userAgentString = "applivery-sdk-android"

lifecycle.addObserver(customTabsManager)
}

override fun onResume() {
super.onResume()

/*
* If this is the first run of the activity, start the authorization intent.
* Note that we do not finish the activity at this point, in order to remain on the back
* stack underneath the authorization activity.
*/
if (!authorizationStarted) {
lifecycleScope.launch {
val uri = presenter.getAuthenticationUri().getOrNull() ?: return@launch
customTabsManager.launch(uri)
authorizationStarted = true
}
scrollBarStyle = View.SCROLLBARS_OUTSIDE_OVERLAY
isScrollbarFadingEnabled = false
webChromeClient = WebChromeClient()
webViewClient = AuthenticatorWebClient(
onLoadFinished = { loadingView.visibility = View.GONE },
onAuthenticationSucceeded = {
presenter.onAuthenticated(it)
finish()
LoginCallbacks.onLogin()
}
)
return
}
CookieManager.getInstance().removeAllCookie()

lifecycleScope.launch {
val uri = presenter.getAuthenticationUri().getOrNull()?.uri ?: return@launch
webView.loadUrl(uri)
/*
* On a subsequent run, it must be determined whether we have returned to this activity
* due to an OAuth2 redirect, or the user canceling the authorization flow. This can
* be done by checking whether a response URI is available, which would be provided by
* RedirectUriReceiverActivity. If it is not, we have returned here due to the user
* pressing the back button, or the authorization activity finishing without
* RedirectUriReceiverActivity having been invoked - this can occur when the user presses
* the back button, or closes the browser tab.
*/
val responseUri = intent.data
if (responseUri != null) {
val bearer = responseUri.getQueryParameter(ReplyQueryBearerKey) ?: return
presenter.onAuthenticated(bearer)
LoginCallbacks.onLogin()
} else {
LoginCallbacks.onCanceled()
}
finish()
}

override fun onBackPressed() {
super.onBackPressed()
LoginCallbacks.onCanceled()
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
setIntent(intent)
}

companion object {

private const val ReplyQueryBearerKey = "bearer"

fun getIntent(context: Context): Intent {
return Intent(context, LoginActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
}

fun createResponseHandlingIntent(context: Context, responseUri: Uri?): Intent {
return getIntent(context)
.setData(responseUri)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,26 @@
*/
package com.applivery.applvsdklib.ui.views.login

import android.net.Uri
import androidx.core.net.toUri
import com.applivery.applvsdklib.features.auth.GetAuthenticationUriUseCase
import com.applivery.base.AppliveryDataManager
import com.applivery.base.domain.PreferencesManager
import com.applivery.base.domain.SessionManager
import com.applivery.base.domain.model.AuthenticationUri

class LoginPresenter(
private val getAuthenticationUri: GetAuthenticationUriUseCase,
private val sessionManager: SessionManager,
private val preferencesManager: PreferencesManager
) {

suspend fun getAuthenticationUri(): Result<AuthenticationUri> {
return getAuthenticationUri.invoke()
suspend fun getAuthenticationUri(): Result<Uri> {
return getAuthenticationUri.invoke().map {
it.uri.toUri()
.buildUpon()
.appendQueryParameter("scheme", AppliveryDataManager.redirectScheme)
.build()
}
}

fun onAuthenticated(bearer: String) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.applivery.applvsdklib.ui.views.login

import android.app.Activity
import android.os.Bundle

class RedirectUriReceiverActivity : Activity() {

public override fun onCreate(savedInstanceBundle: Bundle?) {
super.onCreate(savedInstanceBundle)

startActivity(LoginActivity.createResponseHandlingIntent(this, intent.data))
finish()
}
}
Loading

0 comments on commit 7cb3090

Please sign in to comment.