diff --git a/javasample/src/main/java/com/oursky/authgeartest/MainActivity.java b/javasample/src/main/java/com/oursky/authgeartest/MainActivity.java index 6257ee19..8c206c6d 100644 --- a/javasample/src/main/java/com/oursky/authgeartest/MainActivity.java +++ b/javasample/src/main/java/com/oursky/authgeartest/MainActivity.java @@ -65,6 +65,7 @@ public class MainActivity extends AppCompatActivity { private View mChangePassword; private View mDeleteAccount; private View mFetchUserInfo; + private View mRefreshAccessToken; private View mShowAuthTime; private View mLogout; private MainViewModel viewModel; @@ -104,6 +105,7 @@ protected void onCreate(Bundle savedInstanceState) { mChangePassword = findViewById(R.id.changePassword); mDeleteAccount = findViewById(R.id.deleteAccount); mFetchUserInfo = findViewById(R.id.fetchUserInfo); + mRefreshAccessToken = findViewById(R.id.refreshAccessToken); mShowAuthTime = findViewById(R.id.showAuthTime); mLogout = findViewById(R.id.logout); mUseWebKitWebView = findViewById(R.id.useWebKitWebView); @@ -167,6 +169,7 @@ protected void onCreate(Bundle savedInstanceState) { mChangePassword.setOnClickListener(view -> viewModel.openChangePassword()); mDeleteAccount.setOnClickListener(view -> viewModel.openDeleteAccount()); mFetchUserInfo.setOnClickListener(view -> viewModel.fetchUserInfo()); + mRefreshAccessToken.setOnClickListener(view -> viewModel.refreshAccessToken()); mShowAuthTime.setOnClickListener(view -> viewModel.showAuthTime(this)); mLogout.setOnClickListener(view -> viewModel.logout()); @@ -264,7 +267,10 @@ public void onNothingSelected(AdapterView parent) { mApp2AppstateField.setVisibility(app2appEnabled ? View.VISIBLE : View.GONE); }); - viewModel.sessionState().observe(this, sessionState -> mSessionState.setText(viewModel.sessionState().getValue().toString())); + viewModel.sessionState().observe(this, sessionState -> { + mSessionState.setText(viewModel.sessionState().getValue().toString()); + updateButtonDisabledState(viewModel); + }); viewModel.isLoading().observe(this, isLoading -> updateButtonDisabledState(viewModel)); viewModel.isBiometricEnabled().observe(this, isEnabled -> updateButtonDisabledState(viewModel)); @@ -280,6 +286,16 @@ public void onNothingSelected(AdapterView parent) { builder.create().show(); }); + viewModel.refreshAccessTokenResult().observe(this, message -> { + if (message == null) return; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Refresh Access Token If Needed"); + builder.setMessage(message); + builder.setPositiveButton("OK", (dialogInterface, i) -> { + }); + builder.create().show(); + }); + viewModel.error().observe(this, e -> { if (e == null) return; AlertDialog.Builder builder = new AlertDialog.Builder(this); @@ -358,6 +374,7 @@ private void updateButtonDisabledState(MainViewModel viewModel) { mChangePassword.setEnabled(!isLoading && isConfigured && isLoggedIn); mDeleteAccount.setEnabled(!isLoading && isConfigured && isLoggedIn); mFetchUserInfo.setEnabled(!isLoading && isConfigured && isLoggedIn); + mRefreshAccessToken.setEnabled(!isLoading && isConfigured && isLoggedIn); mShowAuthTime.setEnabled(!isLoading && isConfigured && isLoggedIn); mLogout.setEnabled(!isLoading && isConfigured && isLoggedIn); } diff --git a/javasample/src/main/java/com/oursky/authgeartest/MainViewModel.java b/javasample/src/main/java/com/oursky/authgeartest/MainViewModel.java index 65cd8b03..1c0a3c4c 100644 --- a/javasample/src/main/java/com/oursky/authgeartest/MainViewModel.java +++ b/javasample/src/main/java/com/oursky/authgeartest/MainViewModel.java @@ -29,6 +29,7 @@ import com.oursky.authgear.CancelException; import com.oursky.authgear.ColorScheme; import com.oursky.authgear.CustomTabsUIImplementation; +import com.oursky.authgear.OAuthException; import com.oursky.authgear.OnAuthenticateAnonymouslyListener; import com.oursky.authgear.OnAuthenticateBiometricListener; import com.oursky.authgear.OnAuthenticateListener; @@ -43,6 +44,7 @@ import com.oursky.authgear.OnOpenURLListener; import com.oursky.authgear.OnPromoteAnonymousUserListener; import com.oursky.authgear.OnReauthenticateListener; +import com.oursky.authgear.OnRefreshAccessTokenIfNeededListener; import com.oursky.authgear.OnRefreshIDTokenListener; import com.oursky.authgear.OnWechatAuthCallbackListener; import com.oursky.authgear.OpenAuthorizationURLOptions; @@ -99,6 +101,7 @@ public class MainViewModel extends AndroidViewModel implements AuthgearDelegate final private MutableLiveData mUserInfo = new MutableLiveData<>(null); final private MutableLiveData mSessionState = new MutableLiveData<>(SessionState.UNKNOWN); final private MutableLiveData mError = new MutableLiveData<>(null); + final private MutableLiveData mRefreshAccessTokenResult = new MutableLiveData<>(null); private Intent pendingApp2AppIntent = null; final private MutableLiveData mAuthgearConfigured = new MutableLiveData<>(false); final private MutableLiveData mApp2AppConfirmation = new MutableLiveData<>(null); @@ -239,6 +242,10 @@ public LiveData userInfo() { public LiveData error() { return mError; } + + public LiveData refreshAccessTokenResult() { + return mRefreshAccessTokenResult; + } public LiveData app2appConfirmation() { return mApp2AppConfirmation; } public void configure( @@ -347,8 +354,16 @@ public void onWechatAuthCallbackFailed(Throwable throwable) { } @Override - public void onSessionStateChanged(Authgear container, SessionStateChangeReason reason) { - Log.d(TAG, "Session state=" + container.getSessionState() + " reason=" + reason); + public void onSessionStateChanged(Authgear container, SessionStateChangeReason reason, @Nullable Throwable error) { + Log.d(TAG, "Session state=" + container.getSessionState() + " reason=" + reason + " error=" + error); + if (error instanceof OAuthException) { + String oauthError = ((OAuthException) error).getError(); + if ("invalid_grant".equals(oauthError)) { + Log.d(TAG, "onSessionStateChanged: error is invalid_grant"); + } else if ("invalid_dpop_proof".equals(oauthError)) { + Log.d(TAG, "onSessionStateChanged: error is invalid_dpop_proof"); + } + } this.mSessionState.setValue(container.getSessionState()); } @@ -847,6 +862,26 @@ public void onFetchingUserInfoFailed(@NonNull Throwable throwable) { }); } + // Unlike fetchUserInfo(), this does NOT chain any follow-up request + // after refresh, so the refresh result (e.g. invalid_grant vs + // invalid_dpop_proof) is not masked by a subsequent request made with a + // stale/missing access token. + public void refreshAccessToken() { + mAuthgear.refreshAccessTokenIfNeeded(new OnRefreshAccessTokenIfNeededListener() { + @Override + public void onFinished() { + mRefreshAccessTokenResult.setValue( + "Refreshed access token successfully.\nsessionState: " + mAuthgear.getSessionState() + ); + } + + @Override + public void onFailed(Throwable throwable) { + setError(throwable); + } + }); + } + public void showAuthTime(FragmentActivity activity) { Date nullable = mAuthgear.getAuthTime(); if (nullable != null) { diff --git a/javasample/src/main/res/layout/activity_main.xml b/javasample/src/main/res/layout/activity_main.xml index b8d0b174..79694d80 100644 --- a/javasample/src/main/res/layout/activity_main.xml +++ b/javasample/src/main/res/layout/activity_main.xml @@ -586,6 +586,15 @@ android:text="Fetch user info" tools:ignore="HardcodedText" /> +