Skip to content

Commit 8ba87da

Browse files
authored
Merge 8b06b69 into d91d9aa
2 parents d91d9aa + 8b06b69 commit 8ba87da

File tree

3 files changed

+121
-19
lines changed

3 files changed

+121
-19
lines changed

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/FirebaseAppDistributionImpl.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import com.google.firebase.appdistribution.UpdateStatus;
4747
import com.google.firebase.appdistribution.UpdateTask;
4848
import java.util.concurrent.Executor;
49+
import java.util.concurrent.atomic.AtomicBoolean;
4950

5051
/**
5152
* This class is the "real" implementation of the Firebase App Distribution API which should only be
@@ -86,6 +87,7 @@ class FirebaseAppDistributionImpl implements FirebaseAppDistribution {
8687

8788
private TaskCompletionSource<Void> showSignInDialogTask = null;
8889
private TaskCompletionSource<Void> showUpdateDialogTask = null;
90+
private AtomicBoolean feedbackInProgress = new AtomicBoolean(false);
8991

9092
@VisibleForTesting
9193
FirebaseAppDistributionImpl(
@@ -318,6 +320,12 @@ public void startFeedback(int infoTextResourceId) {
318320

319321
@Override
320322
public void startFeedback(@NonNull CharSequence infoText) {
323+
if (!feedbackInProgress.compareAndSet(/* expect= */ false, /* update= */ true)) {
324+
LogWrapper.getInstance()
325+
.i("Ignoring startFeedback() call because feedback is already in progress");
326+
return;
327+
}
328+
LogWrapper.getInstance().i("Starting feedback");
321329
screenshotTaker
322330
.takeScreenshot()
323331
.addOnFailureListener(
@@ -349,7 +357,10 @@ public void startFeedback(@NonNull CharSequence infoText, @Nullable Uri screensh
349357
taskExecutor,
350358
releaseName -> launchFeedbackActivity(releaseName, infoText, screenshotUri))
351359
.addOnFailureListener(
352-
taskExecutor, e -> LogWrapper.getInstance().e("Failed to launch feedback flow", e));
360+
e -> {
361+
LogWrapper.getInstance().e("Failed to launch feedback flow", e);
362+
feedbackInProgress.set(false);
363+
});
353364
}
354365

355366
private Task<Void> launchFeedbackActivity(
@@ -367,6 +378,11 @@ private Task<Void> launchFeedbackActivity(
367378
});
368379
}
369380

381+
@VisibleForTesting
382+
boolean isFeedbackInProgress() {
383+
return feedbackInProgress.get();
384+
}
385+
370386
@VisibleForTesting
371387
void onActivityResumed(Activity activity) {
372388
if (awaitingSignInDialogConfirmation()) {
@@ -412,10 +428,13 @@ void onActivityDestroyed(@NonNull Activity activity) {
412428
dialogHostActivity = null;
413429
}
414430

415-
// If the feedback activity finishes, clean up the screenshot that was taken before starting
416-
// the activity. If this does not happen for some reason it will be cleaned up the next time
417-
// before taking a new screenshot.
418431
if (activity instanceof FeedbackActivity) {
432+
LogWrapper.getInstance().i("FeedbackActivity destroyed");
433+
feedbackInProgress.set(false);
434+
435+
// If the feedback activity finishes, clean up the screenshot that was taken before starting
436+
// the activity. If this does not happen for some reason it will be cleaned up the next time
437+
// before taking a new screenshot.
419438
screenshotTaker.deleteScreenshot();
420439
}
421440
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ <T> Task<T> applyToForegroundActivityTask(SuccessContinuation<Activity, T> conti
161161
});
162162
}
163163

164-
private Task<Activity> getForegroundActivity() {
164+
Task<Activity> getForegroundActivity() {
165165
synchronized (lock) {
166166
if (currentActivity != null) {
167167
return Tasks.forResult(currentActivity);

firebase-appdistribution/src/test/java/com/google/firebase/appdistribution/impl/FirebaseAppDistributionServiceImplTest.java

Lines changed: 97 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@
3131
import static com.google.firebase.appdistribution.impl.FeedbackActivity.RELEASE_NAME_EXTRA_KEY;
3232
import static com.google.firebase.appdistribution.impl.FeedbackActivity.SCREENSHOT_URI_EXTRA_KEY;
3333
import static com.google.firebase.appdistribution.impl.TestUtils.assertTaskFailure;
34+
import static java.util.stream.Collectors.toList;
3435
import static org.junit.Assert.assertEquals;
3536
import static org.junit.Assert.assertFalse;
3637
import static org.junit.Assert.assertNotNull;
3738
import static org.junit.Assert.assertNull;
3839
import static org.junit.Assert.assertTrue;
40+
import static org.mockito.ArgumentMatchers.any;
3941
import static org.mockito.Mockito.doReturn;
4042
import static org.mockito.Mockito.never;
4143
import static org.mockito.Mockito.spy;
@@ -53,6 +55,7 @@
5355
import android.content.pm.PackageInfo;
5456
import android.net.Uri;
5557
import android.os.Bundle;
58+
import android.util.Log;
5659
import androidx.test.core.app.ApplicationProvider;
5760
import androidx.test.core.content.pm.ApplicationInfoBuilder;
5861
import androidx.test.core.content.pm.PackageInfoBuilder;
@@ -74,15 +77,19 @@
7477
import java.util.concurrent.ExecutorService;
7578
import java.util.concurrent.Executors;
7679
import java.util.concurrent.Future;
80+
import java.util.function.Predicate;
81+
import org.junit.After;
7782
import org.junit.Before;
7883
import org.junit.Test;
7984
import org.junit.runner.RunWith;
80-
import org.mockito.ArgumentCaptor;
8185
import org.mockito.Mock;
8286
import org.mockito.MockitoAnnotations;
8387
import org.robolectric.Robolectric;
8488
import org.robolectric.RobolectricTestRunner;
89+
import org.robolectric.RuntimeEnvironment;
90+
import org.robolectric.android.controller.ActivityController;
8591
import org.robolectric.shadows.ShadowAlertDialog;
92+
import org.robolectric.shadows.ShadowLog;
8693
import org.robolectric.shadows.ShadowPackageManager;
8794

8895
@RunWith(RobolectricTestRunner.class)
@@ -122,6 +129,7 @@ public class FirebaseAppDistributionServiceImplTest {
122129
.setDownloadUrl(TEST_URL);
123130

124131
private FirebaseAppDistributionImpl firebaseAppDistribution;
132+
private ActivityController<TestActivity> activityController;
125133
private TestActivity activity;
126134
private FirebaseApp firebaseApp;
127135
private ExecutorService taskExecutor = Executors.newSingleThreadExecutor();
@@ -190,11 +198,19 @@ public void setup() throws FirebaseAppDistributionException {
190198
packageInfo.setLongVersionCode(INSTALLED_VERSION_CODE);
191199
shadowPackageManager.installPackage(packageInfo);
192200

193-
activity = spy(Robolectric.buildActivity(TestActivity.class).create().get());
201+
activityController = Robolectric.buildActivity(TestActivity.class).setup();
202+
activity = spy(activityController.get());
194203
TestUtils.mockForegroundActivity(mockLifecycleNotifier, activity);
195204
when(mockScreenshotTaker.takeScreenshot()).thenReturn(Tasks.forResult(TEST_SCREENSHOT_URI));
196205
}
197206

207+
@After
208+
public void tearDown() {
209+
if (activityController != null) {
210+
activityController.close();
211+
}
212+
}
213+
198214
@Test
199215
public void checkForNewRelease_whenCheckForNewReleaseFails_throwsError() {
200216
when(mockNewReleaseFetcher.checkForNewRelease())
@@ -635,18 +651,43 @@ public void updateApp_withApkReleaseAvailable_returnsSameApkTask() {
635651
@Test
636652
public void startFeedback_signsInTesterAndStartsActivity() throws InterruptedException {
637653
when(mockReleaseIdentifier.identifyRelease()).thenReturn(Tasks.forResult("release-name"));
654+
638655
firebaseAppDistribution.startFeedback("Some terms and conditions");
639656
TestUtils.awaitAsyncOperations(taskExecutor);
640657

641-
ArgumentCaptor<Intent> argument = ArgumentCaptor.forClass(Intent.class);
642-
verify(activity).startActivity(argument.capture());
643658
verify(mockTesterSignInManager).signInTester();
644-
assertThat(argument.getValue().getStringExtra(RELEASE_NAME_EXTRA_KEY))
645-
.isEqualTo("release-name");
646-
assertThat(argument.getValue().getStringExtra(SCREENSHOT_URI_EXTRA_KEY))
659+
Intent expectedIntent = new Intent(activity, FeedbackActivity.class);
660+
Intent actualIntent = shadowOf(RuntimeEnvironment.getApplication()).getNextStartedActivity();
661+
assertEquals(expectedIntent.getComponent(), actualIntent.getComponent());
662+
assertThat(actualIntent.getStringExtra(RELEASE_NAME_EXTRA_KEY)).isEqualTo("release-name");
663+
assertThat(actualIntent.getStringExtra(SCREENSHOT_URI_EXTRA_KEY))
647664
.isEqualTo(TEST_SCREENSHOT_URI.toString());
648-
assertThat(argument.getValue().getStringExtra(INFO_TEXT_EXTRA_KEY))
665+
assertThat(actualIntent.getStringExtra(INFO_TEXT_EXTRA_KEY))
649666
.isEqualTo("Some terms and conditions");
667+
assertThat(firebaseAppDistribution.isFeedbackInProgress()).isTrue();
668+
}
669+
670+
@Test
671+
public void startFeedback_calledMultipleTimes_onlyStartsOnce() throws InterruptedException {
672+
when(mockReleaseIdentifier.identifyRelease()).thenReturn(Tasks.forResult("release-name"));
673+
674+
firebaseAppDistribution.startFeedback("Some terms and conditions");
675+
firebaseAppDistribution.startFeedback("Some other terms and conditions");
676+
TestUtils.awaitAsyncOperations(taskExecutor);
677+
678+
verify(activity, times(1)).startActivity(any());
679+
}
680+
681+
@Test
682+
public void startFeedback_closingActivity_setsInProgressToFalse() throws InterruptedException {
683+
when(mockReleaseIdentifier.identifyRelease()).thenReturn(Tasks.forResult("release-name"));
684+
685+
firebaseAppDistribution.startFeedback("Some terms and conditions");
686+
TestUtils.awaitAsyncOperations(taskExecutor);
687+
// Simulate destroying the feedback activity
688+
firebaseAppDistribution.onActivityDestroyed(new FeedbackActivity());
689+
690+
assertThat(firebaseAppDistribution.isFeedbackInProgress()).isFalse();
650691
}
651692

652693
@Test
@@ -656,16 +697,58 @@ public void startFeedback_screenshotFails_startActivityWithNoScreenshot()
656697
.thenReturn(
657698
Tasks.forException(new FirebaseAppDistributionException("Error", Status.UNKNOWN)));
658699
when(mockReleaseIdentifier.identifyRelease()).thenReturn(Tasks.forResult("release-name"));
700+
659701
firebaseAppDistribution.startFeedback("Some terms and conditions");
660702
TestUtils.awaitAsyncOperations(taskExecutor);
661703

662-
ArgumentCaptor<Intent> argument = ArgumentCaptor.forClass(Intent.class);
663-
verify(activity).startActivity(argument.capture());
664704
verify(mockTesterSignInManager).signInTester();
665-
assertThat(argument.getValue().getStringExtra(RELEASE_NAME_EXTRA_KEY))
666-
.isEqualTo("release-name");
667-
assertThat(argument.getValue().hasExtra(SCREENSHOT_URI_EXTRA_KEY)).isFalse();
668-
assertThat(argument.getValue().getStringExtra(INFO_TEXT_EXTRA_KEY))
705+
Intent expectedIntent = new Intent(activity, FeedbackActivity.class);
706+
Intent actualIntent = shadowOf(RuntimeEnvironment.getApplication()).getNextStartedActivity();
707+
assertEquals(expectedIntent.getComponent(), actualIntent.getComponent());
708+
assertThat(actualIntent.getStringExtra(RELEASE_NAME_EXTRA_KEY)).isEqualTo("release-name");
709+
assertThat(actualIntent.getStringExtra(SCREENSHOT_URI_EXTRA_KEY)).isNull();
710+
assertThat(actualIntent.getStringExtra(INFO_TEXT_EXTRA_KEY))
669711
.isEqualTo("Some terms and conditions");
712+
assertThat(firebaseAppDistribution.isFeedbackInProgress()).isTrue();
713+
}
714+
715+
@Test
716+
public void startFeedback_signInTesterFails_logsAndSetsInProgressToFalse()
717+
throws InterruptedException {
718+
when(mockReleaseIdentifier.identifyRelease()).thenReturn(Tasks.forResult("release-name"));
719+
FirebaseAppDistributionException exception =
720+
new FirebaseAppDistributionException("Error", Status.UNKNOWN);
721+
when(mockTesterSignInManager.signInTester()).thenReturn(Tasks.forException(exception));
722+
723+
firebaseAppDistribution.startFeedback("Some terms and conditions");
724+
TestUtils.awaitAsyncOperations(taskExecutor);
725+
726+
assertThat(firebaseAppDistribution.isFeedbackInProgress()).isFalse();
727+
assertLoggedError("Failed to launch feedback flow", exception);
728+
}
729+
730+
@Test
731+
public void startFeedback_cantIdentifyRelease_logsAndSetsInProgressToFalse()
732+
throws InterruptedException {
733+
FirebaseAppDistributionException exception =
734+
new FirebaseAppDistributionException("Error", Status.UNKNOWN);
735+
when(mockReleaseIdentifier.identifyRelease()).thenReturn(Tasks.forException(exception));
736+
737+
firebaseAppDistribution.startFeedback("Some terms and conditions");
738+
TestUtils.awaitAsyncOperations(taskExecutor);
739+
740+
assertThat(firebaseAppDistribution.isFeedbackInProgress()).isFalse();
741+
assertLoggedError("Failed to launch feedback flow", exception);
742+
}
743+
744+
private static void assertLoggedError(String partialMessage, Throwable e) {
745+
Predicate<ShadowLog.LogItem> predicate =
746+
log ->
747+
log.type == Log.ERROR
748+
&& log.msg.contains(partialMessage)
749+
&& (e != null ? log.throwable == e : true);
750+
List<ShadowLog.LogItem> matchingLogs =
751+
ShadowLog.getLogs().stream().filter(predicate).collect(toList());
752+
assertThat(matchingLogs).hasSize(1);
670753
}
671754
}

0 commit comments

Comments
 (0)