-
Notifications
You must be signed in to change notification settings - Fork 624
Await foreground activity during sign in #3340
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
abeab3a
053c019
53c9110
c14fd98
7014aa2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,7 +15,7 @@ | |
package com.google.firebase.appdistribution; | ||
|
||
import static com.google.firebase.appdistribution.FirebaseAppDistributionException.Status.AUTHENTICATION_CANCELED; | ||
import static com.google.firebase.appdistribution.FirebaseAppDistributionException.Status.AUTHENTICATION_FAILURE; | ||
import static com.google.firebase.appdistribution.TaskUtils.combineWithResultOf; | ||
import static com.google.firebase.appdistribution.TaskUtils.safeSetTaskException; | ||
import static com.google.firebase.appdistribution.TaskUtils.safeSetTaskResult; | ||
|
||
|
@@ -28,12 +28,13 @@ | |
import androidx.annotation.NonNull; | ||
import androidx.annotation.VisibleForTesting; | ||
import androidx.browser.customtabs.CustomTabsIntent; | ||
import com.google.android.gms.tasks.OnSuccessListener; | ||
import com.google.android.gms.tasks.OnFailureListener; | ||
import com.google.android.gms.tasks.Task; | ||
import com.google.android.gms.tasks.TaskCompletionSource; | ||
import com.google.android.gms.tasks.Tasks; | ||
import com.google.firebase.FirebaseApp; | ||
import com.google.firebase.appdistribution.Constants.ErrorMessages; | ||
import com.google.firebase.appdistribution.FirebaseAppDistributionException.Status; | ||
import com.google.firebase.appdistribution.internal.InstallActivity; | ||
import com.google.firebase.appdistribution.internal.LogWrapper; | ||
import com.google.firebase.appdistribution.internal.SignInResultActivity; | ||
|
@@ -55,6 +56,9 @@ class TesterSignInManager { | |
|
||
private final Object signInTaskLock = new Object(); | ||
|
||
@GuardedBy("signInTaskLock") | ||
private boolean hasBeenSentToBrowserForCurrentTask = false; | ||
|
||
@GuardedBy("signInTaskLock") | ||
private TaskCompletionSource<Void> signInTaskCompletionSource = null; | ||
|
||
|
@@ -87,8 +91,7 @@ class TesterSignInManager { | |
@VisibleForTesting | ||
void onActivityCreated(Activity activity) { | ||
// We call finish() in the onCreate method of the SignInResultActivity, so we must set the | ||
// result | ||
// of the signIn Task in the onActivityCreated callback | ||
// result of the signIn Task in the onActivityCreated callback | ||
if (activity instanceof SignInResultActivity) { | ||
LogWrapper.getInstance().v("Sign in completed"); | ||
this.setSuccessfulSignInResult(); | ||
|
@@ -104,50 +107,74 @@ void onActivityStarted(Activity activity) { | |
return; | ||
} else { | ||
// Throw error if app reentered during sign in | ||
if (this.isCurrentlySigningIn()) { | ||
LogWrapper.getInstance().e("App Resumed without sign in flow completing."); | ||
this.setCanceledAuthenticationError(); | ||
synchronized (signInTaskLock) { | ||
if (awaitingResultFromBrowser()) { | ||
LogWrapper.getInstance().e("App Resumed without sign in flow completing."); | ||
setSignInTaskCompletionError( | ||
new FirebaseAppDistributionException( | ||
Constants.ErrorMessages.AUTHENTICATION_CANCELED, AUTHENTICATION_CANCELED)); | ||
} | ||
} | ||
} | ||
} | ||
|
||
@NonNull | ||
public Task<Void> signInTester() { | ||
|
||
if (signInStorage.getSignInStatus()) { | ||
LogWrapper.getInstance().v(TAG + "Tester is already signed in."); | ||
return Tasks.forResult(null); | ||
} | ||
|
||
synchronized (signInTaskLock) { | ||
if (this.isCurrentlySigningIn()) { | ||
if (signInTaskCompletionSource != null | ||
&& !signInTaskCompletionSource.getTask().isComplete()) { | ||
LogWrapper.getInstance() | ||
.v(TAG + "Detected In-Progress sign in task. Returning the same task."); | ||
return signInTaskCompletionSource.getTask(); | ||
} | ||
|
||
signInTaskCompletionSource = new TaskCompletionSource<>(); | ||
hasBeenSentToBrowserForCurrentTask = false; | ||
|
||
firebaseInstallationsApiProvider | ||
.get() | ||
.getId() | ||
.addOnSuccessListener(getFidGenerationOnSuccessListener()) | ||
.addOnFailureListener( | ||
e -> { | ||
LogWrapper.getInstance().e(TAG + "Fid retrieval failed.", e); | ||
setSignInTaskCompletionError( | ||
new FirebaseAppDistributionException( | ||
ErrorMessages.AUTHENTICATION_ERROR, AUTHENTICATION_FAILURE, e)); | ||
}); | ||
handleTaskFailure(ErrorMessages.AUTHENTICATION_ERROR, Status.AUTHENTICATION_FAILURE)) | ||
.onSuccessTask(combineWithResultOf(() -> lifecycleNotifier.getForegroundActivity())) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this be ok in the case where we get the activity but we don't have the lock and once we get the lock the activity changes? Foreground activity with no lock-> Lock granted -> Different foreground activity is now there but we continue now that we have the lock. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh great catch! That would break. I'll see what I can do about that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made a change in the success listener below, and left some clarifying comments. I think that helps but let me know what you think. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks good to me! |
||
.addOnSuccessListener( | ||
fidAndActivity -> { | ||
// Launch the intent outside of the synchronized block because we don't need to wait | ||
// for the lock, and we don't want to risk the activity leaving the foreground in | ||
// the meantime. | ||
openSignInFlowInBrowser(fidAndActivity.first(), fidAndActivity.second()); | ||
// This synchronized block is required by the @GuardedBy annotation, but is not | ||
// practically required in this case because the only reads of this variable are on | ||
// the main thread, which this callback is also running on. | ||
synchronized (signInTaskLock) { | ||
hasBeenSentToBrowserForCurrentTask = true; | ||
} | ||
}) | ||
// No failures expected here, since getForegroundActivity() will wait indefinitely for a | ||
// foreground activity, but catch any unexpected failures to be safe. | ||
.addOnFailureListener(handleTaskFailure(ErrorMessages.UNKNOWN_ERROR, Status.UNKNOWN)); | ||
|
||
return signInTaskCompletionSource.getTask(); | ||
} | ||
} | ||
|
||
private boolean isCurrentlySigningIn() { | ||
private OnFailureListener handleTaskFailure(String message, Status status) { | ||
return e -> { | ||
LogWrapper.getInstance().e(TAG + message, e); | ||
setSignInTaskCompletionError(new FirebaseAppDistributionException(message, status, e)); | ||
}; | ||
} | ||
|
||
private boolean awaitingResultFromBrowser() { | ||
synchronized (signInTaskLock) { | ||
return signInTaskCompletionSource != null | ||
&& !signInTaskCompletionSource.getTask().isComplete(); | ||
&& !signInTaskCompletionSource.getTask().isComplete() | ||
&& hasBeenSentToBrowserForCurrentTask; | ||
} | ||
} | ||
|
||
|
@@ -157,33 +184,12 @@ private void setSignInTaskCompletionError(FirebaseAppDistributionException e) { | |
} | ||
} | ||
|
||
private void setCanceledAuthenticationError() { | ||
setSignInTaskCompletionError( | ||
new FirebaseAppDistributionException( | ||
Constants.ErrorMessages.AUTHENTICATION_CANCELED, AUTHENTICATION_CANCELED)); | ||
} | ||
|
||
private void setSuccessfulSignInResult() { | ||
synchronized (signInTaskLock) { | ||
safeSetTaskResult(signInTaskCompletionSource, null); | ||
} | ||
} | ||
|
||
private OnSuccessListener<String> getFidGenerationOnSuccessListener() { | ||
return fid -> { | ||
Context context = firebaseApp.getApplicationContext(); | ||
Uri uri = | ||
Uri.parse( | ||
String.format( | ||
SIGNIN_REDIRECT_URL, | ||
firebaseApp.getOptions().getApplicationId(), | ||
fid, | ||
getApplicationName(context), | ||
context.getPackageName())); | ||
openSignInFlowInBrowser(context, uri); | ||
}; | ||
} | ||
|
||
private static String getApplicationName(Context context) { | ||
try { | ||
return context.getApplicationInfo().loadLabel(context.getPackageManager()).toString(); | ||
|
@@ -193,22 +199,30 @@ private static String getApplicationName(Context context) { | |
} | ||
} | ||
|
||
private void openSignInFlowInBrowser(Context applicationContext, Uri uri) { | ||
private void openSignInFlowInBrowser(String fid, Activity activity) { | ||
Context context = firebaseApp.getApplicationContext(); | ||
Uri uri = | ||
Uri.parse( | ||
String.format( | ||
SIGNIN_REDIRECT_URL, | ||
firebaseApp.getOptions().getApplicationId(), | ||
fid, | ||
getApplicationName(context), | ||
context.getPackageName())); | ||
LogWrapper.getInstance().v(TAG + "Opening sign in flow in browser at " + uri); | ||
if (supportsCustomTabs(applicationContext)) { | ||
if (supportsCustomTabs(context)) { | ||
// If we can launch a chrome view, try that. | ||
CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder().build(); | ||
Intent intent = customTabsIntent.intent; | ||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); | ||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||
customTabsIntent.launchUrl(applicationContext, uri); | ||
|
||
customTabsIntent.launchUrl(activity, uri); | ||
} else { | ||
// If we can't launch a chrome view try to launch anything that can handle a URL. | ||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, uri); | ||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); | ||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||
applicationContext.startActivity(browserIntent); | ||
activity.startActivity(browserIntent); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great docs!