diff --git a/.gitignore b/.gitignore index 4a53ef9..a608cb6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ *.war *.ear +# Keep gradle wrapper binary in the project +!me.id.webverify/gradle/wrapper/gradle-wrapper.jar + # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* diff --git a/me.id.webverify/app/build.gradle b/me.id.webverify/app/build.gradle index c8d9c2f..cb3b6c7 100755 --- a/me.id.webverify/app/build.gradle +++ b/me.id.webverify/app/build.gradle @@ -20,6 +20,10 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } repositories { @@ -29,7 +33,6 @@ repositories } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile "com.android.support:appcompat-v7:${rootProject.ext.androidSupportLibraryVersion}" - compile project(':webverifylib') + implementation project(':webverifylib') + implementation "androidx.appcompat:appcompat:${rootProject.ext.androidXAppCompat}" } diff --git a/me.id.webverify/app/src/main/java/me/id/meidwebverify/MainActivity.java b/me.id.webverify/app/src/main/java/me/id/meidwebverify/MainActivity.java index eaa5b26..144a447 100755 --- a/me.id.webverify/app/src/main/java/me/id/meidwebverify/MainActivity.java +++ b/me.id.webverify/app/src/main/java/me/id/meidwebverify/MainActivity.java @@ -1,16 +1,18 @@ package me.id.meidwebverify; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v7.app.ActionBarActivity; import android.view.View; import android.widget.Button; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + import java.util.Locale; +import me.id.webverifylib.IDmeAffiliation; import me.id.webverifylib.IDmeAffiliationType; import me.id.webverifylib.IDmeConnectionType; import me.id.webverifylib.IDmeProfile; @@ -20,7 +22,7 @@ import me.id.webverifylib.listener.IDmeGetProfileListener; import me.id.webverifylib.listener.IDmeScope; -public class MainActivity extends ActionBarActivity { +public class MainActivity extends AppCompatActivity { private String clientId = null; private String secretId = null; private String redirectUri = null; @@ -45,28 +47,18 @@ public void onClick(View view) { } }); - Button btnAddAffiliation = (Button) findViewById(R.id.btnAddAffiliation); - btnAddAffiliation.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - addAffiliation(); - } - }); + Button btnAddAffiliation = findViewById(R.id.btnAddAffiliation); + btnAddAffiliation.setOnClickListener(v -> addAffiliation()); - Button btnAddConnection = (Button) findViewById(R.id.btnAddConnection); - btnAddConnection.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - addConnection(); - } - }); + Button btnAddConnection = findViewById(R.id.btnAddConnection); + btnAddConnection.setOnClickListener(v -> addConnection()); } /** * Method that starts de authentication process */ void login() { - Spinner propRoute = (Spinner) findViewById(R.id.spnProperties); + Spinner propRoute = findViewById(R.id.spnProperties); returnProperties = propRoute.getSelectedItem().toString().equals("Yes"); IDmeWebVerify.getInstance().login(this, Scope.DEFAULT, new IDmeGetAccessTokenListener() { diff --git a/me.id.webverify/app/src/main/java/me/id/meidwebverify/Scope.java b/me.id.webverify/app/src/main/java/me/id/meidwebverify/Scope.java index a2778a5..9b74be9 100644 --- a/me.id.webverify/app/src/main/java/me/id/meidwebverify/Scope.java +++ b/me.id.webverify/app/src/main/java/me/id/meidwebverify/Scope.java @@ -1,6 +1,6 @@ package me.id.meidwebverify; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import me.id.webverifylib.listener.IDmeScope; diff --git a/me.id.webverify/build.gradle b/me.id.webverify/build.gradle index 2e572e5..369d755 100755 --- a/me.id.webverify/build.gradle +++ b/me.id.webverify/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.2' + classpath "com.android.tools.build:gradle:4.0.0" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -16,15 +16,22 @@ buildscript { allprojects { repositories { jcenter() - + google() } } ext { - minSdk = 15 - targetSdk = 25 - buildToolsVersion = '25.0.1' - compileSdkVersion = 25 + minSdk = 21 + targetSdk = 29 + buildToolsVersion = '29.0.3' + compileSdkVersion = 29 + + androidXAnnotations = '1.1.0' + androidXAppCompat = '1.1.0' + androidXBrowser = '1.2.0' +} - androidSupportLibraryVersion = '25.3.1' +// `./gradlew wrapper --gradle-version=[version]` uses the distribution type all +wrapper { + distributionType = Wrapper.DistributionType.ALL } diff --git a/me.id.webverify/gradle.properties b/me.id.webverify/gradle.properties index 7724f2b..87e5674 100755 --- a/me.id.webverify/gradle.properties +++ b/me.id.webverify/gradle.properties @@ -33,3 +33,5 @@ POM_DEVELOPER_NAME=ID.me POM_NAME=ID.me Web Verify (Android) POM_ARTIFACT_ID=webverifylib POM_PACKAGING=aar +android.useAndroidX=true +android.enableJetifier=true diff --git a/me.id.webverify/gradle/wrapper/gradle-wrapper.jar b/me.id.webverify/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..9ab0a83 Binary files /dev/null and b/me.id.webverify/gradle/wrapper/gradle-wrapper.jar differ diff --git a/me.id.webverify/gradle/wrapper/gradle-wrapper.properties b/me.id.webverify/gradle/wrapper/gradle-wrapper.properties old mode 100755 new mode 100644 index e54ef19..84337ad --- a/me.id.webverify/gradle/wrapper/gradle-wrapper.properties +++ b/me.id.webverify/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Wed Mar 15 12:25:03 UYT 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip diff --git a/me.id.webverify/gradlew b/me.id.webverify/gradlew index 91a7e26..cccdd3d 100755 --- a/me.id.webverify/gradlew +++ b/me.id.webverify/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,20 +6,38 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` @@ -154,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/me.id.webverify/gradlew.bat b/me.id.webverify/gradlew.bat old mode 100755 new mode 100644 index aec9973..e95643d --- a/me.id.webverify/gradlew.bat +++ b/me.id.webverify/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/me.id.webverify/webverifylib/build.gradle b/me.id.webverify/webverifylib/build.gradle index bf70610..d176dcd 100755 --- a/me.id.webverify/webverifylib/build.gradle +++ b/me.id.webverify/webverifylib/build.gradle @@ -14,20 +14,17 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } - debug { - // Define a default value for building the tests. This is not used when building any dependent - // libraries or apps. - manifestPlaceholders = [ - idmeAuthRedirectScheme: 'id.me.scheme' - ] - } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile "com.android.support:appcompat-v7:${rootProject.ext.androidSupportLibraryVersion}" - compile "com.android.support:customtabs:${rootProject.ext.androidSupportLibraryVersion}" + implementation "androidx.annotation:annotation:${rootProject.ext.androidXAnnotations}" + implementation "androidx.appcompat:appcompat:${rootProject.ext.androidXAppCompat}" + implementation "androidx.browser:browser:${rootProject.ext.androidXBrowser}" } def isReleaseBuild() { @@ -36,4 +33,4 @@ def isReleaseBuild() { return version.contains("SNAPSHOT") == false } -apply from: '../maven_push.gradle' \ No newline at end of file +apply from: '../maven_push.gradle' diff --git a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/AccessTokenManager.java b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/AccessTokenManager.java index 7a542b9..88904b0 100644 --- a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/AccessTokenManager.java +++ b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/AccessTokenManager.java @@ -2,7 +2,8 @@ import android.content.Context; import android.content.SharedPreferences; -import android.support.annotation.Nullable; + +import androidx.annotation.Nullable; import java.util.HashMap; import java.util.Map; diff --git a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/AsyncSharedPreferenceLoader.java b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/AsyncSharedPreferenceLoader.java index 04c0d0d..9188e88 100644 --- a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/AsyncSharedPreferenceLoader.java +++ b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/AsyncSharedPreferenceLoader.java @@ -3,7 +3,8 @@ import android.content.Context; import android.content.SharedPreferences; import android.os.AsyncTask; -import android.support.annotation.Nullable; + +import androidx.annotation.Nullable; import java.util.concurrent.ExecutionException; diff --git a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeAffiliation.java b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeAffiliation.java new file mode 100644 index 0000000..c609f27 --- /dev/null +++ b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeAffiliation.java @@ -0,0 +1,17 @@ +package me.id.webverifylib; + +import androidx.annotation.NonNull; + +/** + *
Supported affiliations interface. See {@link IDmeAffiliationType} for a subset of supported affiliations.
+ * + *This interface provides flexibility on the affiliations type used with this SDK, in case of having support for + * new affiliations types, a custom implementation of this interface can be passed in. + *
+ * + * @see IDmeAffiliationType + */ +public interface IDmeAffiliation { + @NonNull + String getKey(); +} diff --git a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeAffiliationType.java b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeAffiliationType.java index a003567..13e3c99 100644 --- a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeAffiliationType.java +++ b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeAffiliationType.java @@ -1,9 +1,11 @@ package me.id.webverifylib; +import androidx.annotation.NonNull; + /** * The type of supported affiliations */ -public enum IDmeAffiliationType { +public enum IDmeAffiliationType implements IDmeAffiliation { GOVERNMENT("government"), MILITARY("military"), RESPONDER("responder"), @@ -17,6 +19,8 @@ public enum IDmeAffiliationType { this.key = key; } + @NonNull + @Override public String getKey() { return key; } diff --git a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeConnection.java b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeConnection.java new file mode 100644 index 0000000..38a65c7 --- /dev/null +++ b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeConnection.java @@ -0,0 +1,17 @@ +package me.id.webverifylib; + +import androidx.annotation.NonNull; + +/** + *Supported connections interface. See {@link IDmeConnectionType} for a subset of supported connections.
+ * + *This interface provides flexibility on the connections type used with this SDK, in case of having support for + * new connections types, a custom implementation of this interface can be passed in. + *
+ * + * @see IDmeConnectionType + */ +public interface IDmeConnection { + @NonNull + String getKey(); +} diff --git a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeConnectionType.java b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeConnectionType.java index 9aa5571..54b59c9 100644 --- a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeConnectionType.java +++ b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeConnectionType.java @@ -1,9 +1,11 @@ package me.id.webverifylib; +import androidx.annotation.NonNull; + /** - * Created by remer on 6/2/17. + * The type of supported connections */ -public enum IDmeConnectionType { +public enum IDmeConnectionType implements IDmeConnection { FACEBOOK("facebook"), GOOGLE_PLUS("google"), LINEDIN("linkedin"), @@ -16,6 +18,8 @@ public enum IDmeConnectionType { this.key = key; } + @NonNull + @Override public String getKey() { return key; } diff --git a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeCustomTabsActivity.java b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeCustomTabsActivity.java index b506e07..2504508 100755 --- a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeCustomTabsActivity.java +++ b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeCustomTabsActivity.java @@ -4,11 +4,14 @@ import android.content.ActivityNotFoundException; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; +import android.util.Log; import me.id.webverifylib.exception.UserCanceledException; import me.id.webverifylib.helper.CustomTabsHelper; public class IDmeCustomTabsActivity extends Activity { + private static final int CANCEL_RESULT_CALLBACK_DELAY_IN_MILLIS = 300; public static final String EXTRA_URL = "url"; private boolean shouldCloseCustomTab = true; @@ -18,17 +21,21 @@ public class IDmeCustomTabsActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); url = getIntent().getStringExtra(EXTRA_URL); - if (savedInstanceState == null) { - shouldCloseCustomTab = false; - try { - CustomTabsHelper.getCustomTabIntent(this, url) - .launchUrl(this, Uri.parse(url)); - } catch (ActivityNotFoundException exception) { - IDmeWebVerify.getInstance().notifyFailure( - new ActivityNotFoundException("There isn't an available browser to handle the ID.me oauth flow") - ); + if (url == null) { + Log.w(IDmeWebVerify.TAG, "Url cannot be null, the browser is closed"); finish(); + } else { + shouldCloseCustomTab = false; + try { + CustomTabsHelper.getCustomTabIntent(this, url) + .launchUrl(this, Uri.parse(url)); + } catch (ActivityNotFoundException exception) { + IDmeWebVerify.getInstance().notifyFailure( + new ActivityNotFoundException("There isn't an available browser to handle the ID.me oauth flow") + ); + finish(); + } } } } @@ -37,10 +44,22 @@ protected void onCreate(Bundle savedInstanceState) { protected void onResume() { super.onResume(); if (shouldCloseCustomTab) { - if (IDmeWebVerify.getCurrentState() != null) { - IDmeWebVerify.getInstance().notifyFailure(new UserCanceledException()); + if (IDmeWebVerify.getCurrentState() == null) { + finish(); + } else { + // In some devices, when the callback url isCalled, this activity is resumed before the + // `RedirectUriReceiverActivity` is invoked (the callback activity), producing a cancellation state. + // The delay is done to check if an activity callback was queued. + new Handler().postDelayed(() -> { + if (IDmeWebVerify.getCurrentState() != null && !IDmeWebVerify.isExecutingBackgroundTaskState()) { + IDmeWebVerify.getInstance().notifyFailure(new UserCanceledException()); + } + if (!isDestroyed() && !IDmeWebVerify.isExecutingBackgroundTaskState()) { + finish(); + } + }, CANCEL_RESULT_CALLBACK_DELAY_IN_MILLIS); + IDmeWebVerify.setupSdkStabilizationTime(CANCEL_RESULT_CALLBACK_DELAY_IN_MILLIS); } - finish(); } shouldCloseCustomTab = true; } diff --git a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeProfile.java b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeProfile.java index 4747f38..04e9722 100644 --- a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeProfile.java +++ b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeProfile.java @@ -2,7 +2,8 @@ import android.os.Parcel; import android.os.Parcelable; -import android.support.annotation.NonNull; + +import androidx.annotation.NonNull; import org.json.JSONException; import org.json.JSONObject; diff --git a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeWebVerify.java b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeWebVerify.java index aee6225..8ab0101 100755 --- a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeWebVerify.java +++ b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/IDmeWebVerify.java @@ -4,11 +4,18 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.os.Handler; import android.util.Log; +import androidx.annotation.CheckResult; +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Calendar; +import java.util.Date; import java.util.Locale; +import java.util.Set; import me.id.webverifylib.exception.IDmeException; import me.id.webverifylib.exception.UnauthenticatedException; @@ -42,6 +49,8 @@ public final class IDmeWebVerify { private static final String RESPONSE_TYPE_VALUE = "code"; private static final String LOGOUT_TYPE_VALUE = "logout"; + private static final int STABILIZATION_DELAY_VALUE_IN_MILLIS = 50; + private static Uri idMeWebVerifyAccessTokenUri; private static Uri idMeWebVerifyGetCommonUri; private static Uri idMeWebVerifyGetLogoutUri; @@ -54,6 +63,9 @@ public final class IDmeWebVerify { private static String clientSecret; private static boolean initialized; private static State currentState; + private static boolean isExecutingBackgroundTask; + @Nullable + private static Date sdkStabilizationDate = null; private IDmeGetAccessTokenListener accessTokenCallback = null; private IDmeCompletableListener completableCallback = null; @@ -135,6 +147,7 @@ private void checkInitialization() { * @param scope The type of group verification. * @param listener The listener that will be called when the login process is finished. */ + @MainThread public void login(@NonNull Activity activity, @NonNull IDmeScope scope, @NonNull IDmeGetAccessTokenListener listener) { login(activity, scope, null, listener); } @@ -151,17 +164,62 @@ public void login(@NonNull Activity activity, @NonNull IDmeScope scope, @NonNull * @throws UnauthenticatedException if the auth information is not valid * @throws IDmeException if something went wrong */ + @MainThread public void login(@NonNull Activity activity, @NonNull IDmeScope scope, @Nullable LoginType loginType, @NonNull IDmeGetAccessTokenListener listener) { checkInitialization(); - setCurrentState(State.LOGIN, scope); - accessTokenCallback = listener; if (loginType == null) { loginType = LoginType.SIGN_IN; } - String requestUrl = createURL(scope, loginType); - openCustomTabActivity(activity, requestUrl); + Uri requestUrl = createURL(scope, loginType); + login(activity, requestUrl, listener); + } + + /** + * Starts the login process using a custom url. + * + * @param activity which will be used to start the login activity + * @param loginUri The url used to perform the login process. + * @param listener The listener that will be called when the login process is finished. + * @throws UserCanceledException if the user cancel the action + * @throws UnauthenticatedException if the auth information is not valid + * @throws IDmeException if something went wrong + */ + @MainThread + public void login(@NonNull Activity activity, @NonNull Uri loginUri, @NonNull IDmeGetAccessTokenListener listener) { + long sdkStabilizationTimeInMillis = + sdkStabilizationDate == null ? 0 : sdkStabilizationDate.getTime() - Calendar.getInstance().getTimeInMillis(); + if (sdkStabilizationTimeInMillis <= 0) { + performLogin(activity, loginUri, listener); + } else { + new Handler() + .postDelayed(() -> performLogin(activity, loginUri, listener), sdkStabilizationTimeInMillis); + } + } + + private void performLogin( + @NonNull Activity activity, + @NonNull Uri loginUri, + @NonNull IDmeGetAccessTokenListener listener + ) { + checkInitialization(); + String scopeStr = loginUri.getQueryParameter(PARAM_SCOPE_TYPE); + if (scopeStr == null) { + throw new IDmeException("Malformed Url: scope is missing"); + } + String clientId = loginUri.getQueryParameter(PARAM_CLIENT_ID); + if (clientId != null && !clientId.equals(IDmeWebVerify.clientId)) { + throw new IDmeException("Invalid Url: the provided client id doesn't match with the library client id."); + } + + IDmeScope scope = () -> scopeStr; + + setCurrentState(State.LOGIN, scope); + accessTokenCallback = listener; + Uri requestUrl = addPKCEParams(loginUri); + requestUrl = addRedirectUrl(requestUrl); + openCustomTabActivity(activity, requestUrl.toString()); } private void openCustomTabActivity(@NonNull Activity activity, String requestUrl) { @@ -184,6 +242,24 @@ public void getAccessToken(@NonNull IDmeScope scope, @NonNull final IDmeGetAcces getAccessToken(scope, false, listener); } + /** + * Returns the current access token if it's valid. + * + * @param scope The type of group verification. + * @return The current access token + */ + @SuppressWarnings("unused") + @Nullable + public String getLocalAccessToken(@NonNull IDmeScope scope) { + checkInitialization(); + AuthToken token = accessTokenManager.getToken(scope); + if (token != null && token.isValidAccessToken()) { + return token.getAccessToken(); + } else { + return null; + } + } + /** * Returns an access token * @@ -270,23 +346,23 @@ public void logOut(@NonNull Activity activity, @NonNull IDmeScope scope, @NonNul /** * Starts the process of adding a new affiliation type * - * @param activity Which will be used to start the login activity. - * @param scope The type of group verification. - * @param affiliationType The affiliation that will be registered. - * @param listener The listener that will be called when the registration process finished. + * @param activity Which will be used to start the login activity. + * @param scope The type of group verification. + * @param affiliation The affiliation that will be registered. + * @param listener The listener that will be called when the registration process finished. * @throws UserCanceledException if the user cancel the action * @throws UnauthenticatedException if the auth information is not valid * @throws IDmeException if something went wrong */ public void registerAffiliation(@NonNull Activity activity, @NonNull IDmeScope scope, - IDmeAffiliationType affiliationType, + @NonNull IDmeAffiliation affiliation, @NonNull IDmeCompletableListener listener) { checkInitialization(); setCurrentState(State.REGISTER_AFFILIATION, scope); completableCallback = listener; - String requestUrl = createRegisterAffiliationUrl(affiliationType); + String requestUrl = createRegisterAffiliationUrl(affiliation); openCustomTabActivity(activity, requestUrl); } @@ -301,8 +377,9 @@ public void registerAffiliation(@NonNull Activity activity, * @throws UnauthenticatedException if the auth information is not valid * @throws IDmeException if something went wrong */ - public void registerConnection(@NonNull Activity activity, @NonNull IDmeScope scope, - @NonNull IDmeConnectionType connectionType, + public void registerConnection(@NonNull Activity activity, + @NonNull IDmeScope scope, + @NonNull IDmeConnection connectionType, @NonNull IDmeCompletableListener listener) { checkInitialization(); setCurrentState(State.REGISTER_CONNECTION, scope); @@ -372,6 +449,7 @@ private void clearListenersAndClearState() { accessTokenCallback = null; completableCallback = null; currentState = null; + isExecutingBackgroundTask = false; } /** @@ -379,14 +457,43 @@ private void clearListenersAndClearState() { * * @return URl with redirect uri, client id and scope */ - private String createURL(@NonNull IDmeScope scope, @NonNull LoginType loginType) { + @CheckResult + private Uri createURL(@NonNull IDmeScope scope, @NonNull LoginType loginType) { return getCommonUri() - .appendQueryParameter(PARAM_CODE_CHALLENGE, currentState.getCodeChallenge()) - .appendQueryParameter(PARAM_CODE_CHALLENGE_METHOD, currentState.getCodeVerifierMethod()) .appendQueryParameter(PARAM_SCOPE_TYPE, scope.getScopeId()) .appendQueryParameter(SIGN_TYPE_KEY, loginType.getId()) - .build() - .toString(); + .build(); + } + + /** + * Adds the PKCE params + * + * @return URl with PKCE params + */ + @CheckResult + private Uri addPKCEParams(@NonNull Uri uri) { + return uri.buildUpon() + .appendQueryParameter(PARAM_CODE_CHALLENGE, currentState.getCodeChallenge()) + .appendQueryParameter(PARAM_CODE_CHALLENGE_METHOD, currentState.getCodeVerifierMethod()) + .build(); + } + + /** + * Adds the redirect url + * + * @return URl including the redirect callback. + */ + @CheckResult + private Uri addRedirectUrl(@NonNull Uri uri) { + final Set