Skip to content

Fad only show UI for basic config #3260

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

Merged
merged 11 commits into from
Jan 4, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

package com.google.firebase.app.distribution;

import static com.google.firebase.app.distribution.FirebaseAppDistributionException.Status.AUTHENTICATION_CANCELED;
import static com.google.firebase.app.distribution.FirebaseAppDistributionException.Status.AUTHENTICATION_FAILURE;
import static com.google.firebase.app.distribution.FirebaseAppDistributionException.Status.UPDATE_NOT_AVAILABLE;
import static com.google.firebase.app.distribution.TaskUtils.safeSetTaskException;
Expand All @@ -27,6 +28,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
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.app.distribution.Constants.ErrorMessages;
Expand All @@ -37,6 +39,7 @@
import com.google.firebase.installations.FirebaseInstallationsApi;

public class FirebaseAppDistribution {

private static final int UNKNOWN_RELEASE_FILE_SIZE = -1;

private final FirebaseApp firebaseApp;
Expand All @@ -60,6 +63,7 @@ public class FirebaseAppDistribution {
private Task<AppDistributionRelease> cachedCheckForNewReleaseTask;
private AlertDialog updateDialog;
private boolean updateDialogShown;
private AlertDialog signInDialog;

/** Constructor for FirebaseAppDistribution */
@VisibleForTesting
Expand Down Expand Up @@ -129,12 +133,16 @@ public UpdateTask updateIfNewReleaseAvailable() {
if (cachedUpdateIfNewReleaseTask != null && !cachedUpdateIfNewReleaseTask.isComplete()) {
return cachedUpdateIfNewReleaseTask;
}

cachedUpdateIfNewReleaseTask = new UpdateTaskImpl();
}
checkForNewRelease()
.onSuccessTask(
release -> {

showSignInDialog()
.onSuccessTask(unused -> signInTester())
.onSuccessTask(unused -> checkForNewRelease())
.continueWithTask(
checkForNewReleaseTask -> {
AppDistributionRelease release =
checkForNewReleaseTask.getResult(); // error will propagate to on failure listener
if (release == null) {
postProgressToCachedUpdateIfNewReleaseTask(
UpdateProgress.builder()
Expand All @@ -143,30 +151,123 @@ public UpdateTask updateIfNewReleaseAvailable() {
.setUpdateStatus(UpdateStatus.NEW_RELEASE_NOT_AVAILABLE)
.build());
setCachedUpdateIfNewReleaseResult();
return Tasks.forResult(null);
UpdateTaskImpl updateTask = new UpdateTaskImpl();
updateTask.setResult();
return updateTask;
}
return showUpdateAlertDialog(release);
return showUpdateAlertDialog(release)
.onSuccessTask(
r ->
updateApp(true)
.addOnProgressListener(
this::postProgressToCachedUpdateIfNewReleaseTask)
.addOnSuccessListener(unused -> setCachedUpdateIfNewReleaseResult())
.addOnFailureListener(
this::setCachedUpdateIfNewReleaseCompletionError));
})
.addOnFailureListener(
e -> {
postProgressToCachedUpdateIfNewReleaseTask(
UpdateProgress.builder()
.setApkFileTotalBytes(UNKNOWN_RELEASE_FILE_SIZE)
.setApkBytesDownloaded(UNKNOWN_RELEASE_FILE_SIZE)
.setUpdateStatus(UpdateStatus.NEW_RELEASE_CHECK_FAILED)
.build());
setCachedUpdateIfNewReleaseCompletionError(
e,
new FirebaseAppDistributionException(
Constants.ErrorMessages.NETWORK_ERROR,
FirebaseAppDistributionException.Status.NETWORK_FAILURE));
});
.addOnFailureListener(this::setCachedUpdateIfNewReleaseCompletionError);

synchronized (updateIfNewReleaseTaskLock) {
return cachedUpdateIfNewReleaseTask;
}
}

private Task<Void> showSignInDialog() {
if (isTesterSignedIn()) {
return Tasks.forResult(null);
}

TaskCompletionSource<Void> showDialogTask = new TaskCompletionSource<>();

Activity currentActivity = lifecycleNotifier.getCurrentActivity();
if (currentActivity == null) {
LogWrapper.getInstance().e("No foreground activity found.");
showDialogTask.setException(
new FirebaseAppDistributionException(
ErrorMessages.APP_BACKGROUNDED,
FirebaseAppDistributionException.Status.UPDATE_NOT_AVAILABLE));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like there are scenarios like this where the status doesn't exactly align with what is going on (here its not that an update isn't available). Our logs do have the more specific case to give the user a better sense of whats going on, but do we want to look at these statuses to make them more specific?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really good call. I've added an error message enum to our API and made a note to add it to the API addendum.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, good callout. There are a bunch of places like this where the error status and/or message does not align with the actual situation. I'm currently going through them all as part of the error handling cleanup, so @rachaprince if you want I can handle this in a followup since this was just moved code.

For this case though, looking at the statuses, AUTHENTICATION_FAILED might work if we consider "authentication" to be synonymous with "tester sign in."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I think your new "no foreground activity" error seems like a good one to add, could be useful elsewhere. AUTHENTICATION_FAILED probably implies that the actual authentication was not allowed, which would be misleading.

}

signInDialog = new AlertDialog.Builder(currentActivity).create();
Context context = firebaseApp.getApplicationContext();
signInDialog.setTitle(context.getString(R.string.signin_dialog_title));
signInDialog.setMessage(context.getString(R.string.singin_dialog_message));

signInDialog.setButton(
AlertDialog.BUTTON_POSITIVE,
context.getString(R.string.singin_yes_button),
(dialogInterface, i) -> showDialogTask.setResult(null));

signInDialog.setButton(
AlertDialog.BUTTON_NEGATIVE,
context.getString(R.string.singin_no_button),
(dialogInterface, i) ->
showDialogTask.setException(
new FirebaseAppDistributionException(
ErrorMessages.AUTHENTICATION_CANCELED, AUTHENTICATION_CANCELED)));

signInDialog.setOnCancelListener(
dialogInterface ->
showDialogTask.setException(
new FirebaseAppDistributionException(
ErrorMessages.AUTHENTICATION_CANCELED, AUTHENTICATION_CANCELED)));

signInDialog.show();
return showDialogTask.getTask();
}

private Task<Void> showUpdateAlertDialog(AppDistributionRelease newRelease) {
TaskCompletionSource<Void> showUpdateDialogTask = new TaskCompletionSource<>();
Activity currentActivity = lifecycleNotifier.getCurrentActivity();
if (currentActivity == null) {
LogWrapper.getInstance().e("No foreground activity found.");
return getErrorUpdateTask(
new FirebaseAppDistributionException(
ErrorMessages.APP_BACKGROUNDED,
FirebaseAppDistributionException.Status.UPDATE_NOT_AVAILABLE));
}

Context context = firebaseApp.getApplicationContext();

updateDialog = new AlertDialog.Builder(currentActivity).create();
updateDialog.setTitle(context.getString(R.string.update_dialog_title));

StringBuilder message =
new StringBuilder(
String.format(
"Version %s (%s) is available.",
newRelease.getDisplayVersion(), newRelease.getVersionCode()));

if (newRelease.getReleaseNotes() != null && !newRelease.getReleaseNotes().isEmpty()) {
message.append(String.format("\n\nRelease notes: %s", newRelease.getReleaseNotes()));
}
updateDialog.setMessage(message);

updateDialog.setButton(
AlertDialog.BUTTON_POSITIVE,
context.getString(R.string.update_yes_button),
(dialogInterface, i) -> showUpdateDialogTask.setResult(null));

updateDialog.setButton(
AlertDialog.BUTTON_NEGATIVE,
context.getString(R.string.update_no_button),
(dialogInterface, i) ->
showUpdateDialogTask.setException(
new FirebaseAppDistributionException(
ErrorMessages.UPDATE_CANCELED, Status.INSTALLATION_CANCELED)));

updateDialog.setOnCancelListener(
dialogInterface ->
showUpdateDialogTask.setException(
new FirebaseAppDistributionException(
ErrorMessages.UPDATE_CANCELED, Status.INSTALLATION_CANCELED)));

updateDialog.show();
updateDialogShown = true;

return showUpdateDialogTask.getTask();
}

/** Signs in the App Distribution tester. Presents the tester with a Google sign in UI */
@NonNull
public Task<Void> signInTester() {
Expand Down Expand Up @@ -195,6 +296,13 @@ public synchronized Task<AppDistributionRelease> checkForNewRelease() {
})
.addOnFailureListener(
e -> {
postProgressToCachedUpdateIfNewReleaseTask(
UpdateProgress.builder()
.setApkBytesDownloaded(UNKNOWN_RELEASE_FILE_SIZE)
.setApkFileTotalBytes(UNKNOWN_RELEASE_FILE_SIZE)
.setUpdateStatus(UpdateStatus.NEW_RELEASE_CHECK_FAILED)
.build());

if (e instanceof FirebaseAppDistributionException
&& ((FirebaseAppDistributionException) e).getErrorCode()
== AUTHENTICATION_FAILURE) {
Expand Down Expand Up @@ -294,113 +402,39 @@ AppDistributionReleaseInternal getCachedNewRelease() {
}
}

private UpdateTaskImpl showUpdateAlertDialog(AppDistributionRelease newRelease) {
Context context = firebaseApp.getApplicationContext();
Activity currentActivity = lifecycleNotifier.getCurrentActivity();
if (currentActivity == null) {
LogWrapper.getInstance().e("No foreground activity found.");
UpdateTaskImpl updateTask = new UpdateTaskImpl();
updateTask.setException(
new FirebaseAppDistributionException(
ErrorMessages.APP_BACKGROUNDED,
FirebaseAppDistributionException.Status.UPDATE_NOT_AVAILABLE));
return updateTask;
}
updateDialog = new AlertDialog.Builder(currentActivity).create();
updateDialog.setTitle(context.getString(R.string.update_dialog_title));

StringBuilder message =
new StringBuilder(
String.format(
"Version %s (%s) is available.",
newRelease.getDisplayVersion(), newRelease.getVersionCode()));

if (newRelease.getReleaseNotes() != null && !newRelease.getReleaseNotes().isEmpty()) {
message.append(String.format("\n\nRelease notes: %s", newRelease.getReleaseNotes()));
}

updateDialog.setMessage(message);
updateDialog.setButton(
AlertDialog.BUTTON_POSITIVE,
context.getString(R.string.update_yes_button),
(dialogInterface, i) -> {
synchronized (updateIfNewReleaseTaskLock) {
// show download progress in notification manager
updateApp(true)
.addOnProgressListener(this::postProgressToCachedUpdateIfNewReleaseTask)
.addOnSuccessListener(unused -> setCachedUpdateIfNewReleaseResult())
.addOnFailureListener(cachedUpdateIfNewReleaseTask::setException);
}
});

updateDialog.setButton(
AlertDialog.BUTTON_NEGATIVE,
context.getString(R.string.update_no_button),
(dialogInterface, i) -> dismissUpdateDialogCallback());

updateDialog.setOnCancelListener(
dialogInterface -> {
dismissUpdateDialogCallback();
});

updateDialog.show();
updateDialogShown = true;
synchronized (updateIfNewReleaseTaskLock) {
return cachedUpdateIfNewReleaseTask;
}
}

private void dismissUpdateDialogCallback() {
synchronized (updateIfNewReleaseTaskLock) {
postProgressToCachedUpdateIfNewReleaseTask(
UpdateProgress.builder()
.setApkFileTotalBytes(UNKNOWN_RELEASE_FILE_SIZE)
.setApkBytesDownloaded(UNKNOWN_RELEASE_FILE_SIZE)
.setUpdateStatus(UpdateStatus.UPDATE_CANCELED)
.build());
setCachedUpdateIfNewReleaseCompletionError(
new FirebaseAppDistributionException(
ErrorMessages.UPDATE_CANCELED, Status.INSTALLATION_CANCELED));
}
}

private void setCachedUpdateIfNewReleaseCompletionError(FirebaseAppDistributionException e) {
private void setCachedUpdateIfNewReleaseCompletionError(Exception e) {
synchronized (updateIfNewReleaseTaskLock) {
safeSetTaskException(cachedUpdateIfNewReleaseTask, e);
}
dismissUpdateDialog();
}

private void setCachedUpdateIfNewReleaseCompletionError(
Exception e, FirebaseAppDistributionException defaultFirebaseException) {
if (e instanceof FirebaseAppDistributionException) {
setCachedUpdateIfNewReleaseCompletionError((FirebaseAppDistributionException) e);
} else {
setCachedUpdateIfNewReleaseCompletionError(defaultFirebaseException);
}
dismissDialogs();
}

private void postProgressToCachedUpdateIfNewReleaseTask(UpdateProgress progress) {
synchronized (updateIfNewReleaseTaskLock) {
cachedUpdateIfNewReleaseTask.updateProgress(progress);
if (cachedUpdateIfNewReleaseTask != null && !cachedUpdateIfNewReleaseTask.isComplete()) {
cachedUpdateIfNewReleaseTask.updateProgress(progress);
}
}
}

private void setCachedUpdateIfNewReleaseResult() {
synchronized (updateIfNewReleaseTaskLock) {
safeSetTaskResult(cachedUpdateIfNewReleaseTask);
}
dismissUpdateDialog();
dismissDialogs();
}

private void dismissUpdateDialog() {
private void dismissDialogs() {
if (signInDialog != null && signInDialog.isShowing()) {
signInDialog.dismiss();
}
if (updateDialog != null) {
updateDialog.dismiss();
updateDialogShown = false;
}
}

private UpdateTask getErrorUpdateTask(Exception e) {
private UpdateTaskImpl getErrorUpdateTask(Exception e) {
UpdateTaskImpl updateTask = new UpdateTaskImpl();
updateTask.setException(e);
return updateTask;
Expand Down
Loading