Skip to content

Commit 44de82c

Browse files
lfkelloggjeremyjiang-dev
authored andcommitted
Show dialogs on UI thread (#3464)
* Show dialogs on UI thread * Fix test names
1 parent 372dd33 commit 44de82c

File tree

3 files changed

+122
-72
lines changed

3 files changed

+122
-72
lines changed

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/FirebaseAppDistribution.java

Lines changed: 71 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -215,34 +215,40 @@ private Task<Void> showSignInConfirmationDialog(Activity hostActivity) {
215215
showSignInDialogTask = new TaskCompletionSource<>();
216216
}
217217

218-
signInConfirmationDialog = new AlertDialog.Builder(hostActivity).create();
219218
dialogHostActivity = hostActivity;
220219

221-
Context context = firebaseApp.getApplicationContext();
222-
signInConfirmationDialog.setTitle(context.getString(R.string.signin_dialog_title));
223-
signInConfirmationDialog.setMessage(context.getString(R.string.singin_dialog_message));
224-
225-
signInConfirmationDialog.setButton(
226-
AlertDialog.BUTTON_POSITIVE,
227-
context.getString(R.string.singin_yes_button),
228-
(dialogInterface, i) -> showSignInDialogTask.setResult(null));
229-
230-
signInConfirmationDialog.setButton(
231-
AlertDialog.BUTTON_NEGATIVE,
232-
context.getString(R.string.singin_no_button),
233-
(dialogInterface, i) ->
234-
showSignInDialogTask.setException(
235-
new FirebaseAppDistributionException(
236-
ErrorMessages.AUTHENTICATION_CANCELED, AUTHENTICATION_CANCELED)));
237-
238-
signInConfirmationDialog.setOnCancelListener(
239-
dialogInterface ->
240-
showSignInDialogTask.setException(
241-
new FirebaseAppDistributionException(
242-
ErrorMessages.AUTHENTICATION_CANCELED, AUTHENTICATION_CANCELED)));
243-
244-
signInConfirmationDialog.show();
245-
220+
// We may not be on the main (UI) thread in some cases, specifically if the developer calls
221+
// the basic config from the background. If we are already on the main thread, this will
222+
// execute immediately.
223+
hostActivity.runOnUiThread(
224+
() -> {
225+
signInConfirmationDialog = new AlertDialog.Builder(hostActivity).create();
226+
227+
Context context = firebaseApp.getApplicationContext();
228+
signInConfirmationDialog.setTitle(context.getString(R.string.signin_dialog_title));
229+
signInConfirmationDialog.setMessage(context.getString(R.string.singin_dialog_message));
230+
231+
signInConfirmationDialog.setButton(
232+
AlertDialog.BUTTON_POSITIVE,
233+
context.getString(R.string.singin_yes_button),
234+
(dialogInterface, i) -> showSignInDialogTask.setResult(null));
235+
236+
signInConfirmationDialog.setButton(
237+
AlertDialog.BUTTON_NEGATIVE,
238+
context.getString(R.string.singin_no_button),
239+
(dialogInterface, i) ->
240+
showSignInDialogTask.setException(
241+
new FirebaseAppDistributionException(
242+
ErrorMessages.AUTHENTICATION_CANCELED, AUTHENTICATION_CANCELED)));
243+
244+
signInConfirmationDialog.setOnCancelListener(
245+
dialogInterface ->
246+
showSignInDialogTask.setException(
247+
new FirebaseAppDistributionException(
248+
ErrorMessages.AUTHENTICATION_CANCELED, AUTHENTICATION_CANCELED)));
249+
250+
signInConfirmationDialog.show();
251+
});
246252
return showSignInDialogTask.getTask();
247253
}
248254

@@ -420,42 +426,47 @@ private Task<Void> showUpdateConfirmationDialog(
420426
}
421427

422428
Context context = firebaseApp.getApplicationContext();
423-
424-
updateConfirmationDialog = new AlertDialog.Builder(hostActivity).create();
425429
dialogHostActivity = hostActivity;
426-
updateConfirmationDialog.setTitle(context.getString(R.string.update_dialog_title));
427430

428-
StringBuilder message =
429-
new StringBuilder(
430-
String.format(
431-
"Version %s (%s) is available.",
432-
newRelease.getDisplayVersion(), newRelease.getVersionCode()));
433-
434-
if (newRelease.getReleaseNotes() != null && !newRelease.getReleaseNotes().isEmpty()) {
435-
message.append(String.format("\n\nRelease notes: %s", newRelease.getReleaseNotes()));
436-
}
437-
updateConfirmationDialog.setMessage(message);
438-
439-
updateConfirmationDialog.setButton(
440-
AlertDialog.BUTTON_POSITIVE,
441-
context.getString(R.string.update_yes_button),
442-
(dialogInterface, i) -> showUpdateDialogTask.setResult(null));
443-
444-
updateConfirmationDialog.setButton(
445-
AlertDialog.BUTTON_NEGATIVE,
446-
context.getString(R.string.update_no_button),
447-
(dialogInterface, i) ->
448-
showUpdateDialogTask.setException(
449-
new FirebaseAppDistributionException(
450-
ErrorMessages.UPDATE_CANCELED, Status.INSTALLATION_CANCELED)));
451-
452-
updateConfirmationDialog.setOnCancelListener(
453-
dialogInterface ->
454-
showUpdateDialogTask.setException(
455-
new FirebaseAppDistributionException(
456-
ErrorMessages.UPDATE_CANCELED, Status.INSTALLATION_CANCELED)));
457-
458-
updateConfirmationDialog.show();
431+
// We should already be on the main (UI) thread here, but be explicit just to be safe. If we are
432+
// already on the main thread, this will execute immediately.
433+
hostActivity.runOnUiThread(
434+
() -> {
435+
updateConfirmationDialog = new AlertDialog.Builder(hostActivity).create();
436+
updateConfirmationDialog.setTitle(context.getString(R.string.update_dialog_title));
437+
438+
StringBuilder message =
439+
new StringBuilder(
440+
String.format(
441+
"Version %s (%s) is available.",
442+
newRelease.getDisplayVersion(), newRelease.getVersionCode()));
443+
444+
if (newRelease.getReleaseNotes() != null && !newRelease.getReleaseNotes().isEmpty()) {
445+
message.append(String.format("\n\nRelease notes: %s", newRelease.getReleaseNotes()));
446+
}
447+
updateConfirmationDialog.setMessage(message);
448+
449+
updateConfirmationDialog.setButton(
450+
AlertDialog.BUTTON_POSITIVE,
451+
context.getString(R.string.update_yes_button),
452+
(dialogInterface, i) -> showUpdateDialogTask.setResult(null));
453+
454+
updateConfirmationDialog.setButton(
455+
AlertDialog.BUTTON_NEGATIVE,
456+
context.getString(R.string.update_no_button),
457+
(dialogInterface, i) ->
458+
showUpdateDialogTask.setException(
459+
new FirebaseAppDistributionException(
460+
ErrorMessages.UPDATE_CANCELED, Status.INSTALLATION_CANCELED)));
461+
462+
updateConfirmationDialog.setOnCancelListener(
463+
dialogInterface ->
464+
showUpdateDialogTask.setException(
465+
new FirebaseAppDistributionException(
466+
ErrorMessages.UPDATE_CANCELED, Status.INSTALLATION_CANCELED)));
467+
468+
updateConfirmationDialog.show();
469+
});
459470

460471
return showUpdateDialogTask.getTask();
461472
}

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/FirebaseAppDistributionLifecycleNotifier.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ interface OnActivityDestroyedListener {
9898
/**
9999
* Apply a function to a foreground activity, when one is available, returning a {@link Task} that
100100
* will complete immediately after the function is applied.
101+
*
102+
* <p>The consumer function will be called immediately once the activity is available. This may be
103+
* on the main thread or the calling thread, depending on whether or not there is already a
104+
* foreground activity available when this method is called.
101105
*/
102106
Task<Void> applyToForegroundActivity(ActivityConsumer consumer) {
103107
return getForegroundActivity()
@@ -117,6 +121,10 @@ Task<Void> applyToForegroundActivity(ActivityConsumer consumer) {
117121
/**
118122
* Apply a function to a foreground activity, when one is available, returning a {@link Task} that
119123
* will complete with the result of the Task returned by that function.
124+
*
125+
* <p>The continuation function will be called immediately once the activity is available. This
126+
* may be on the main thread or the calling thread, depending on whether or not there is already a
127+
* foreground activity available when this method is called.
120128
*/
121129
<T> Task<T> applyToForegroundActivityTask(SuccessContinuation<Activity, T> continuation) {
122130
return getForegroundActivity()

firebase-appdistribution/src/test/java/com/google/firebase/appdistribution/FirebaseAppDistributionTest.java

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@
6161
import com.google.firebase.installations.InstallationTokenResult;
6262
import java.util.ArrayList;
6363
import java.util.List;
64+
import java.util.concurrent.ExecutionException;
65+
import java.util.concurrent.ExecutorService;
66+
import java.util.concurrent.Executors;
67+
import java.util.concurrent.Future;
6468
import org.junit.Before;
6569
import org.junit.Test;
6670
import org.junit.runner.RunWith;
@@ -231,7 +235,7 @@ public void updateApp_whenNotSignedIn_throwsError() {
231235
}
232236

233237
@Test
234-
public void updateToNewRelease_whenNewAabReleaseAvailable_showsUpdateDialog() {
238+
public void updateIfNewReleaseAvailable_whenNewAabReleaseAvailable_showsUpdateDialog() {
235239
when(mockNewReleaseFetcher.checkForNewRelease())
236240
.thenReturn(Tasks.forResult((TEST_RELEASE_NEWER_AAB_INTERNAL.build())));
237241

@@ -248,7 +252,20 @@ public void updateToNewRelease_whenNewAabReleaseAvailable_showsUpdateDialog() {
248252
}
249253

250254
@Test
251-
public void updateToNewRelease_whenReleaseNotesEmpty_doesNotShowReleaseNotes() {
255+
public void updateIfNewReleaseAvailable_fromABackgroundThread_showsUpdateDialog()
256+
throws InterruptedException {
257+
when(mockNewReleaseFetcher.checkForNewRelease())
258+
.thenReturn(Tasks.forResult((TEST_RELEASE_NEWER_AAB_INTERNAL.build())));
259+
260+
ExecutorService executorService = Executors.newSingleThreadExecutor();
261+
executorService.submit(() -> firebaseAppDistribution.updateIfNewReleaseAvailable());
262+
TestUtils.awaitAsyncOperations(executorService);
263+
264+
assertAlertDialogShown();
265+
}
266+
267+
@Test
268+
public void updateIfNewReleaseAvailable_whenReleaseNotesEmpty_doesNotShowReleaseNotes() {
252269
when(mockNewReleaseFetcher.checkForNewRelease())
253270
.thenReturn(Tasks.forResult((TEST_RELEASE_NEWER_AAB_INTERNAL.setReleaseNotes("").build())));
254271

@@ -263,7 +280,7 @@ public void updateToNewRelease_whenReleaseNotesEmpty_doesNotShowReleaseNotes() {
263280
}
264281

265282
@Test
266-
public void updateToNewRelease_whenNoReleaseAvailable_updateDialogNotShown() {
283+
public void updateIfNewReleaseAvailable_whenNoReleaseAvailable_updateDialogNotShown() {
267284
when(mockNewReleaseFetcher.checkForNewRelease()).thenReturn(Tasks.forResult(null));
268285

269286
UpdateTask task = firebaseAppDistribution.updateIfNewReleaseAvailable();
@@ -277,7 +294,7 @@ public void updateToNewRelease_whenNoReleaseAvailable_updateDialogNotShown() {
277294
}
278295

279296
@Test
280-
public void updateToNewRelease_whenActivityBackgrounded_updateDialogNotShown() {
297+
public void updateIfNewReleaseAvailable_whenActivityBackgrounded_updateDialogNotShown() {
281298
when(mockNewReleaseFetcher.checkForNewRelease()).thenReturn(Tasks.forResult(null));
282299

283300
UpdateTask task = firebaseAppDistribution.updateIfNewReleaseAvailable();
@@ -291,7 +308,7 @@ public void updateToNewRelease_whenActivityBackgrounded_updateDialogNotShown() {
291308
}
292309

293310
@Test
294-
public void updateToNewRelease_whenSignInCancelled_checkForUpdateNotCalled() {
311+
public void updateIfNewReleaseAvailable_whenSignInCancelled_checkForUpdateNotCalled() {
295312
when(mockSignInStorage.getSignInStatus()).thenReturn(false);
296313
when(mockTesterSignInManager.signInTester())
297314
.thenReturn(
@@ -310,7 +327,7 @@ public void updateToNewRelease_whenSignInCancelled_checkForUpdateNotCalled() {
310327
}
311328

312329
@Test
313-
public void updateToNewRelease_whenSignInFailed_checkForUpdateNotCalled() {
330+
public void updateIfNewReleaseAvailable_whenSignInFailed_checkForUpdateNotCalled() {
314331
when(mockSignInStorage.getSignInStatus()).thenReturn(false);
315332
when(mockTesterSignInManager.signInTester())
316333
.thenReturn(
@@ -327,7 +344,7 @@ public void updateToNewRelease_whenSignInFailed_checkForUpdateNotCalled() {
327344
}
328345

329346
@Test
330-
public void updateToNewRelease_whenDialogDismissed_taskFails() {
347+
public void updateIfNewReleaseAvailable_whenDialogDismissed_taskFails() {
331348
when(mockNewReleaseFetcher.checkForNewRelease())
332349
.thenReturn(Tasks.forResult(TEST_RELEASE_NEWER_AAB_INTERNAL.build()));
333350

@@ -341,7 +358,7 @@ public void updateToNewRelease_whenDialogDismissed_taskFails() {
341358
}
342359

343360
@Test
344-
public void updateToNewRelease_whenDialogCanceled_taskFails() {
361+
public void updateIfNewReleaseAvailable_whenDialogCanceled_taskFails() {
345362
when(mockNewReleaseFetcher.checkForNewRelease())
346363
.thenReturn(Tasks.forResult(TEST_RELEASE_NEWER_AAB_INTERNAL.build()));
347364

@@ -356,7 +373,7 @@ public void updateToNewRelease_whenDialogCanceled_taskFails() {
356373
}
357374

358375
@Test
359-
public void updateToNewRelease_whenCheckForUpdateFails_updateAppNotCalled() {
376+
public void updateIfNewReleaseAvailable_whenCheckForUpdateFails_updateAppNotCalled() {
360377
when(mockNewReleaseFetcher.checkForNewRelease())
361378
.thenReturn(
362379
Tasks.forException(
@@ -376,7 +393,7 @@ public void updateToNewRelease_whenCheckForUpdateFails_updateAppNotCalled() {
376393
}
377394

378395
@Test
379-
public void updateToNewRelease_whenTesterIsSignedIn_doesNotOpenDialog() {
396+
public void updateIfNewReleaseAvailable_whenTesterIsSignedIn_doesNotOpenDialog() {
380397
when(mockSignInStorage.getSignInStatus()).thenReturn(true);
381398

382399
firebaseAppDistribution.updateIfNewReleaseAvailable();
@@ -400,7 +417,7 @@ public void signInTester_whenDialogDismissed_taskFails() {
400417
}
401418

402419
@Test
403-
public void updateToNewRelease_whenSignInDialogCanceled_taskFails() {
420+
public void updateIfNewReleaseAvailable_whenSignInDialogCanceled_taskFails() {
404421
when(mockSignInStorage.getSignInStatus()).thenReturn(false);
405422
Task signInTask = firebaseAppDistribution.updateIfNewReleaseAvailable();
406423

@@ -429,7 +446,7 @@ public void signOutTester_setsSignInStatusFalse() {
429446
}
430447

431448
@Test
432-
public void updateToNewRelease_receiveProgressUpdateFromUpdateApp() {
449+
public void updateIfNewReleaseAvailable_receiveProgressUpdateFromUpdateApp() {
433450
AppDistributionReleaseInternal newRelease = TEST_RELEASE_NEWER_AAB_INTERNAL.build();
434451
when(mockNewReleaseFetcher.checkForNewRelease()).thenReturn(Tasks.forResult(newRelease));
435452
UpdateTaskImpl mockTask = new UpdateTaskImpl();
@@ -455,6 +472,20 @@ public void updateToNewRelease_receiveProgressUpdateFromUpdateApp() {
455472
assertEquals(UpdateStatus.DOWNLOADING, progressEvents.get(0).getUpdateStatus());
456473
}
457474

475+
@Test
476+
public void updateIfNewReleaseAvailable_fromABackgroundThread_showsSignInDialog()
477+
throws InterruptedException, ExecutionException {
478+
when(mockSignInStorage.getSignInStatus()).thenReturn(false);
479+
480+
ExecutorService executorService = Executors.newSingleThreadExecutor();
481+
Future<UpdateTask> future =
482+
executorService.submit(() -> firebaseAppDistribution.updateIfNewReleaseAvailable());
483+
TestUtils.awaitAsyncOperations(executorService);
484+
485+
assertAlertDialogShown();
486+
assertFalse(((UpdateTask) future.get()).isComplete());
487+
}
488+
458489
@Test
459490
public void updateIfNewReleaseAvailable_whenScreenRotates_signInConfirmationDialogReappears() {
460491
when(mockSignInStorage.getSignInStatus()).thenReturn(false);

0 commit comments

Comments
 (0)