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 params = uri.getQueryParameterNames(); + final Uri.Builder newUri = uri.buildUpon().clearQuery(); + for (String param : params) { + if (!param.equals(PARAM_REDIRECT_URI)) { + newUri.appendQueryParameter(param, uri.getQueryParameter(param)); + } + } + newUri.appendQueryParameter(PARAM_REDIRECT_URI, redirectUri); + return newUri.build(); } /** @@ -427,14 +534,14 @@ private String createLogoutRequestUrl(@Nullable IDmeScope scope) { /** * Creates the URL for adding a new affiliation type * - * @param affiliationType The affiliation type that should be registered + * @param affiliation The affiliation type that should be registered * @return URL with proper formatted request */ - private String createRegisterAffiliationUrl(IDmeAffiliationType affiliationType) { + private String createRegisterAffiliationUrl(IDmeAffiliation affiliation) { return getCommonUri() .appendQueryParameter(PARAM_CODE_CHALLENGE, currentState.getCodeChallenge()) .appendQueryParameter(PARAM_CODE_CHALLENGE_METHOD, currentState.getCodeVerifierMethod()) - .appendQueryParameter(PARAM_SCOPE_TYPE, affiliationType.getKey()) + .appendQueryParameter(PARAM_SCOPE_TYPE, affiliation.getKey()) .build() .toString(); } @@ -446,7 +553,7 @@ private String createRegisterAffiliationUrl(IDmeAffiliationType affiliationType) * @param scope The type of group verification. * @return URL: with proper formatted request */ - private String createRegisterConnectionUrl(IDmeConnectionType connectionType, IDmeScope scope) { + private String createRegisterConnectionUrl(IDmeConnection connectionType, IDmeScope scope) { return getCommonUri() .appendQueryParameter(PARAM_CODE_CHALLENGE, currentState.getCodeChallenge()) .appendQueryParameter(PARAM_CODE_CHALLENGE_METHOD, currentState.getCodeVerifierMethod()) @@ -475,6 +582,22 @@ private synchronized void setCurrentState(State state, IDmeScope scope) { } } + static void setExecutingBackgroundTaskState() { + isExecutingBackgroundTask = true; + } + + static boolean isExecutingBackgroundTaskState() { + return isExecutingBackgroundTask; + } + + synchronized static void setupSdkStabilizationTime(int taskTimeOutInMillis) { + sdkStabilizationDate = new Date( + Calendar.getInstance().getTimeInMillis() + + taskTimeOutInMillis + + STABILIZATION_DELAY_VALUE_IN_MILLIS + ); + } + static String getIdMeWebVerifyAccessTokenUri() { return idMeWebVerifyAccessTokenUri.toString(); } diff --git a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/RedirectUriReceiverActivity.java b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/RedirectUriReceiverActivity.java index 22ebe9f..49ada3c 100644 --- a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/RedirectUriReceiverActivity.java +++ b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/RedirectUriReceiverActivity.java @@ -1,10 +1,13 @@ package me.id.webverifylib; import android.app.Activity; +import android.app.PendingIntent; +import android.content.Intent; import android.os.Bundle; -import android.support.annotation.Nullable; import android.util.Log; +import androidx.annotation.Nullable; + import me.id.webverifylib.exception.IDmeException; import me.id.webverifylib.listener.IDmeAccessTokenManagerListener; import me.id.webverifylib.networking.GetAccessTokenConnectionTask; @@ -14,13 +17,13 @@ public class RedirectUriReceiverActivity extends Activity { @Override public void onSuccess(AuthToken authToken) { IDmeWebVerify.getInstance().notifySuccess(authToken); - sendResult(RESULT_OK); + sendResult(RESULT_OK, true); } @Override public void onError(Throwable throwable) { IDmeWebVerify.getInstance().notifyFailure(throwable); - sendResult(RESULT_CANCELED); + sendResult(RESULT_CANCELED, true); } }; @@ -31,34 +34,46 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { if (currentState == null) { Log.w(IDmeWebVerify.TAG, "Activity was created but there is not an initialized process"); - sendResult(RESULT_CANCELED); + sendResult(RESULT_CANCELED, false); return; } if (getIntent() == null || getIntent().getData() == null) { IDmeWebVerify.getInstance().notifyFailure(new IDmeException("Null intent or invalid data was received")); - sendResult(RESULT_CANCELED); + sendResult(RESULT_CANCELED, false); return; } if (currentState == State.LOGOUT) { IDmeWebVerify.getInstance().notifyLogoutSuccess(); - sendResult(RESULT_OK); + sendResult(RESULT_OK, true); return; } String code = getIntent().getData().getQueryParameter(IDmeWebVerify.PARAM_CODE); if (code == null || code.isEmpty()) { IDmeWebVerify.getInstance().notifyFailure(new IDmeException("An error has occurred getting the auth token")); - sendResult(RESULT_CANCELED); + sendResult(RESULT_CANCELED, true); } else { + IDmeWebVerify.setExecutingBackgroundTaskState(); new GetAccessTokenConnectionTask(IDmeWebVerify.getAccessTokenQuery(code), authCodeListener, currentState.getScope()) .execute(IDmeWebVerify.getIdMeWebVerifyAccessTokenUri()); } } - private void sendResult(int resultCode) { + private void sendResult(int resultCode, boolean notifyTabActivity) { setResult(resultCode); + try { + if (notifyTabActivity) { + // MOB-944: send an intent to the activity that started the browser. This is needed due to + // RedirectUriReceiverActivity is opened in the browser's stack, so when this activity finishes, + // the result is that the browser will stay visible. + Intent intent = new Intent(this, IDmeCustomTabsActivity.class); + PendingIntent.getActivity(this, 0, intent, 0).send(); + } + } catch (PendingIntent.CanceledException ex) { + ex.printStackTrace(); + } finish(); } } diff --git a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/State.java b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/State.java index 73c25ef..e61be3f 100644 --- a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/State.java +++ b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/State.java @@ -1,6 +1,6 @@ package me.id.webverifylib; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import me.id.webverifylib.helper.CodeVerifierUtil; import me.id.webverifylib.listener.IDmeScope; diff --git a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/helper/AccessTokenHelper.java b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/helper/AccessTokenHelper.java index 0682c06..b36bcca 100644 --- a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/helper/AccessTokenHelper.java +++ b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/helper/AccessTokenHelper.java @@ -1,7 +1,7 @@ package me.id.webverifylib.helper; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.json.JSONException; import org.json.JSONObject; diff --git a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/helper/CustomTabsHelper.java b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/helper/CustomTabsHelper.java index 0a805a1..185818e 100644 --- a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/helper/CustomTabsHelper.java +++ b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/helper/CustomTabsHelper.java @@ -6,9 +6,10 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; -import android.support.customtabs.CustomTabsIntent; import android.text.TextUtils; +import androidx.browser.customtabs.CustomTabsIntent; + import java.util.ArrayList; import java.util.List; @@ -35,8 +36,6 @@ public static CustomTabsIntent getCustomTabIntent(Context context, String url) { if (packageName != null) { customTabsIntent.intent.setPackage(packageName); } - customTabsIntent.intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_CLEAR_TOP - | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); return customTabsIntent; } diff --git a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/helper/ObjectHelper.java b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/helper/ObjectHelper.java index 9e8e126..5fe2eb5 100644 --- a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/helper/ObjectHelper.java +++ b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/helper/ObjectHelper.java @@ -1,11 +1,12 @@ package me.id.webverifylib.helper; -import android.support.annotation.Nullable; import android.util.Base64; import android.util.Base64InputStream; import android.util.Base64OutputStream; import android.util.Log; +import androidx.annotation.Nullable; + import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; diff --git a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/helper/Preconditions.java b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/helper/Preconditions.java index 683c66a..5a1beca 100644 --- a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/helper/Preconditions.java +++ b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/helper/Preconditions.java @@ -1,9 +1,10 @@ package me.id.webverifylib.helper; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.text.TextUtils; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.util.Collection; /** diff --git a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/listener/IDmeScope.java b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/listener/IDmeScope.java index 311959c..556b705 100644 --- a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/listener/IDmeScope.java +++ b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/listener/IDmeScope.java @@ -1,6 +1,6 @@ package me.id.webverifylib.listener; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; /** * Created by mirland on 22/12/16. diff --git a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/networking/GetAccessTokenConnectionTask.java b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/networking/GetAccessTokenConnectionTask.java index 94dee4c..4aee2ed 100644 --- a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/networking/GetAccessTokenConnectionTask.java +++ b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/networking/GetAccessTokenConnectionTask.java @@ -1,7 +1,8 @@ package me.id.webverifylib.networking; import android.os.AsyncTask; -import android.support.annotation.NonNull; + +import androidx.annotation.NonNull; import java.io.OutputStream; import java.net.HttpURLConnection; diff --git a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/networking/GetProfileConnectionTask.java b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/networking/GetProfileConnectionTask.java index 5e0d326..436c34e 100644 --- a/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/networking/GetProfileConnectionTask.java +++ b/me.id.webverify/webverifylib/src/main/java/me/id/webverifylib/networking/GetProfileConnectionTask.java @@ -1,9 +1,10 @@ package me.id.webverifylib.networking; import android.os.AsyncTask; -import android.support.annotation.NonNull; import android.util.Log; +import androidx.annotation.NonNull; + import org.json.JSONException; import java.io.BufferedReader;