Skip to content

Commit f10c72b

Browse files
Merge branch 'master' into mrschmidt/simplifylocalstore
2 parents 7179d4b + f25efea commit f10c72b

File tree

20 files changed

+368
-171
lines changed

20 files changed

+368
-171
lines changed

ci/fireci/fireciplugins/macrobenchmark.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ async def _execute_benchmark_tests(self):
184184
args += ['--type', 'instrumentation']
185185
args += ['--app', app_apk_path]
186186
args += ['--test', test_apk_path]
187-
args += ['--device', 'model=flame,version=30,locale=en,orientation=portrait']
187+
args += ['--device', 'model=redfin,version=30,locale=en,orientation=portrait']
188188
args += ['--directories-to-pull', '/sdcard/Download']
189189
args += ['--results-bucket', f'gs://{self.test_results_bucket}']
190190
args += ['--results-dir', self.test_results_dir]

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

Lines changed: 52 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@
1616

1717
import static com.google.firebase.appdistribution.FirebaseAppDistributionException.Status.DOWNLOAD_FAILURE;
1818
import static com.google.firebase.appdistribution.FirebaseAppDistributionException.Status.NETWORK_FAILURE;
19+
import static com.google.firebase.appdistribution.TaskUtils.combineWithResultOf;
20+
import static com.google.firebase.appdistribution.TaskUtils.runAsyncInTask;
1921
import static com.google.firebase.appdistribution.TaskUtils.safeSetTaskException;
2022

2123
import android.app.Activity;
22-
import android.content.Context;
2324
import android.content.Intent;
2425
import android.net.Uri;
2526
import androidx.annotation.GuardedBy;
@@ -42,29 +43,28 @@ class AabUpdater {
4243
private final HttpsUrlConnectionFactory httpsUrlConnectionFactory;
4344
private final Executor executor;
4445

46+
private final Object updateAabLock = new Object();
47+
4548
@GuardedBy("updateAabLock")
4649
private UpdateTaskImpl cachedUpdateTask;
4750

4851
@GuardedBy("updateAabLock")
4952
private AppDistributionReleaseInternal aabReleaseInProgress;
5053

51-
private final Object updateAabLock = new Object();
52-
private final Context context;
54+
@GuardedBy("updateAabLock")
55+
private boolean hasBeenSentToPlayForCurrentTask = false;
5356

54-
AabUpdater(@NonNull Context context) {
57+
AabUpdater() {
5558
this(
56-
context,
5759
FirebaseAppDistributionLifecycleNotifier.getInstance(),
5860
new HttpsUrlConnectionFactory(),
5961
Executors.newSingleThreadExecutor());
6062
}
6163

6264
AabUpdater(
63-
@NonNull Context context,
6465
@NonNull FirebaseAppDistributionLifecycleNotifier lifecycleNotifier,
6566
@NonNull HttpsUrlConnectionFactory httpsUrlConnectionFactory,
6667
@NonNull Executor executor) {
67-
this.context = context;
6868
this.lifecycleNotifier = lifecycleNotifier;
6969
this.httpsUrlConnectionFactory = httpsUrlConnectionFactory;
7070
lifecycleNotifier.addOnActivityStartedListener(this::onActivityStarted);
@@ -76,9 +76,13 @@ void onActivityStarted(Activity activity) {
7676
if (activity instanceof SignInResultActivity || activity instanceof InstallActivity) {
7777
return;
7878
}
79-
// If app resumes and aab update task is in progress, assume that installation didn't happen so
80-
// cancel the task
81-
this.tryCancelAabUpdateTask();
79+
// If app resumes and update is in progress, assume that installation didn't happen and cancel
80+
// the task
81+
synchronized (updateAabLock) {
82+
if (awaitingUpdateFromPlay()) {
83+
this.tryCancelAabUpdateTask();
84+
}
85+
}
8286
}
8387

8488
UpdateTaskImpl updateAab(@NonNull AppDistributionReleaseInternal newRelease) {
@@ -89,7 +93,15 @@ UpdateTaskImpl updateAab(@NonNull AppDistributionReleaseInternal newRelease) {
8993

9094
cachedUpdateTask = new UpdateTaskImpl();
9195
aabReleaseInProgress = newRelease;
92-
redirectToPlayForAabUpdate(newRelease.getDownloadUrl());
96+
hasBeenSentToPlayForCurrentTask = false;
97+
98+
// On a background thread, fetch the redirect URL and open it in the Play app
99+
runAsyncInTask(executor, () -> fetchDownloadRedirectUrl(newRelease.getDownloadUrl()))
100+
.onSuccessTask(combineWithResultOf(() -> lifecycleNotifier.getForegroundActivity()))
101+
.addOnSuccessListener(
102+
urlAndActivity ->
103+
openRedirectUrlInPlay(urlAndActivity.first(), urlAndActivity.second()))
104+
.addOnFailureListener(this::setUpdateTaskCompletionError);
93105

94106
return cachedUpdateTask;
95107
}
@@ -138,38 +150,30 @@ private static boolean isRedirectResponse(int responseCode) {
138150
return responseCode >= 300 && responseCode < 400;
139151
}
140152

141-
private void redirectToPlayForAabUpdate(String downloadUrl) {
142-
// The 302 redirect is obtained here to open the play store directly and avoid opening chrome
143-
executor.execute( // Execute the network calls on a background thread
144-
() -> {
145-
String redirectUrl;
146-
try {
147-
redirectUrl = fetchDownloadRedirectUrl(downloadUrl);
148-
} catch (FirebaseAppDistributionException e) {
149-
setUpdateTaskCompletionError(e);
150-
return;
151-
}
152-
153-
Intent updateIntent = new Intent(Intent.ACTION_VIEW);
154-
updateIntent.setData(Uri.parse(redirectUrl));
155-
updateIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
156-
LogWrapper.getInstance().v(TAG + "Redirecting to play");
157-
158-
synchronized (updateAabLock) {
159-
context.startActivity(updateIntent);
160-
cachedUpdateTask.updateProgress(
161-
UpdateProgress.builder()
162-
.setApkBytesDownloaded(-1)
163-
.setApkFileTotalBytes(-1)
164-
.setUpdateStatus(UpdateStatus.REDIRECTED_TO_PLAY)
165-
.build());
166-
}
167-
});
153+
private void openRedirectUrlInPlay(String redirectUrl, Activity hostActivity) {
154+
Intent updateIntent = new Intent(Intent.ACTION_VIEW);
155+
updateIntent.setData(Uri.parse(redirectUrl));
156+
updateIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
157+
LogWrapper.getInstance().v(TAG + "Redirecting to play");
158+
159+
// Launch the intent before the synchronized block to avoid failing to update in the rare
160+
// scenario where the activity moves to the background while we're awaiting the lock.
161+
hostActivity.startActivity(updateIntent);
162+
163+
synchronized (updateAabLock) {
164+
cachedUpdateTask.updateProgress(
165+
UpdateProgress.builder()
166+
.setApkBytesDownloaded(-1)
167+
.setApkFileTotalBytes(-1)
168+
.setUpdateStatus(UpdateStatus.REDIRECTED_TO_PLAY)
169+
.build());
170+
hasBeenSentToPlayForCurrentTask = true;
171+
}
168172
}
169173

170-
private void setUpdateTaskCompletionError(FirebaseAppDistributionException e) {
174+
private void setUpdateTaskCompletionError(Exception e) {
171175
synchronized (updateAabLock) {
172-
safeSetTaskException(cachedUpdateTask, e);
176+
safeSetTaskException(cachedUpdateTask, FirebaseAppDistributionException.wrap(e));
173177
}
174178
}
175179

@@ -183,4 +187,12 @@ void tryCancelAabUpdateTask() {
183187
ReleaseUtils.convertToAppDistributionRelease(aabReleaseInProgress)));
184188
}
185189
}
190+
191+
private boolean awaitingUpdateFromPlay() {
192+
synchronized (updateAabLock) {
193+
return cachedUpdateTask != null
194+
&& !cachedUpdateTask.isComplete()
195+
&& hasBeenSentToPlayForCurrentTask;
196+
}
197+
}
186198
}

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ public class FirebaseAppDistribution {
6464
private Task<AppDistributionRelease> cachedCheckForNewReleaseTask;
6565
private AlertDialog updateDialog;
6666
private AlertDialog signInConfirmationDialog;
67+
private boolean remakeUpdateDialog = false;
68+
private boolean remakeSignInConfirmationDialog = false;
6769

6870
/** Constructor for FirebaseAppDistribution */
6971
@VisibleForTesting
@@ -83,6 +85,7 @@ public class FirebaseAppDistribution {
8385
this.signInStorage = signInStorage;
8486
this.lifecycleNotifier = lifecycleNotifier;
8587
lifecycleNotifier.addOnActivityDestroyedListener(this::onActivityDestroyed);
88+
lifecycleNotifier.addOnActivityCreatedListener(this::onActivityCreated);
8689
}
8790

8891
/** Constructor for FirebaseAppDistribution */
@@ -99,7 +102,7 @@ public class FirebaseAppDistribution {
99102
new FirebaseAppDistributionTesterApiClient(),
100103
firebaseInstallationsApiProvider),
101104
new ApkUpdater(firebaseApp, new ApkInstaller()),
102-
new AabUpdater(firebaseApp.getApplicationContext()),
105+
new AabUpdater(),
103106
signInStorage,
104107
lifecycleNotifier);
105108
}
@@ -326,6 +329,14 @@ void onActivityDestroyed(@NonNull Activity activity) {
326329
// SignInResult is internal to the SDK and is destroyed after creation
327330
return;
328331
}
332+
if (activity.isChangingConfigurations()) {
333+
remakeSignInConfirmationDialog =
334+
signInConfirmationDialog != null && signInConfirmationDialog.isShowing();
335+
remakeUpdateDialog = updateDialog != null && updateDialog.isShowing();
336+
dismissDialogs();
337+
return;
338+
}
339+
329340
if (signInConfirmationDialog != null && signInConfirmationDialog.isShowing()) {
330341
setCachedUpdateIfNewReleaseCompletionError(
331342
new FirebaseAppDistributionException(
@@ -339,6 +350,19 @@ void onActivityDestroyed(@NonNull Activity activity) {
339350
}
340351
}
341352

353+
void onActivityCreated(Activity activity) {
354+
if (remakeSignInConfirmationDialog) {
355+
remakeSignInConfirmationDialog = false;
356+
showSignInConfirmationDialog(activity);
357+
} else if (remakeUpdateDialog) {
358+
remakeUpdateDialog = false;
359+
synchronized (cachedNewReleaseLock) {
360+
showUpdateAlertDialog(
361+
activity, ReleaseUtils.convertToAppDistributionRelease(cachedNewRelease));
362+
}
363+
}
364+
}
365+
342366
@VisibleForTesting
343367
void setCachedNewRelease(@Nullable AppDistributionReleaseInternal newRelease) {
344368
synchronized (cachedNewReleaseLock) {

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import androidx.annotation.NonNull;
1818
import androidx.annotation.Nullable;
1919
import com.google.firebase.FirebaseException;
20+
import com.google.firebase.appdistribution.Constants.ErrorMessages;
2021

2122
/** Possible exceptions thrown in FirebaseAppDistribution */
2223
public class FirebaseAppDistributionException extends FirebaseException {
@@ -98,4 +99,13 @@ public AppDistributionRelease getRelease() {
9899
public Status getErrorCode() {
99100
return status;
100101
}
102+
103+
static FirebaseAppDistributionException wrap(Throwable t) {
104+
// We never want to wrap a FirebaseAppDistributionException
105+
if (t instanceof FirebaseAppDistributionException) {
106+
return (FirebaseAppDistributionException) t;
107+
}
108+
return new FirebaseAppDistributionException(
109+
String.format("%s: %s", ErrorMessages.UNKNOWN_ERROR, t.getMessage()), Status.UNKNOWN, t);
110+
}
101111
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ interface OnActivityDestroyedListener {
8383
void onDestroyed(Activity activity);
8484
}
8585

86+
/**
87+
* Get a {@link Task} that will succeed with a result of the app's foregrounded {@link Activity},
88+
* when one is available.
89+
*
90+
* <p>The returned task will never fail. It will instead remain pending indefinitely until some
91+
* activity comes to the foreground.
92+
*/
8693
Task<Activity> getForegroundActivity() {
8794
synchronized (lock) {
8895
if (currentActivity != null) {

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

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,31 @@
1414

1515
package com.google.firebase.appdistribution;
1616

17+
import com.google.android.gms.tasks.SuccessContinuation;
1718
import com.google.android.gms.tasks.Task;
1819
import com.google.android.gms.tasks.TaskCompletionSource;
1920
import com.google.android.gms.tasks.Tasks;
20-
import com.google.firebase.appdistribution.Constants.ErrorMessages;
21+
import com.google.auto.value.AutoValue;
2122
import com.google.firebase.appdistribution.FirebaseAppDistributionException.Status;
2223
import com.google.firebase.appdistribution.internal.LogWrapper;
2324
import java.util.concurrent.Executor;
2425

2526
class TaskUtils {
2627
private static final String TAG = "TaskUtils:";
2728

29+
/**
30+
* A functional interface to wrap a function that returns some result of a possibly long-running
31+
* operation, and could potentially throw a {@link FirebaseAppDistributionException}.
32+
*/
2833
interface Operation<TResult> {
2934
TResult run() throws FirebaseAppDistributionException;
3035
}
3136

37+
/** A functional interface to wrap a function that produces a {@link Task}. */
38+
interface TaskSource<TResult> {
39+
Task<TResult> get();
40+
}
41+
3242
/**
3343
* Runs a long running operation inside a {@link Task}, wrapping any errors in {@link
3444
* FirebaseAppDistributionException}.
@@ -51,10 +61,8 @@ static <TResult> Task<TResult> runAsyncInTask(Executor executor, Operation<TResu
5161
() -> {
5262
try {
5363
taskCompletionSource.setResult(operation.run());
54-
} catch (FirebaseAppDistributionException e) {
55-
taskCompletionSource.setException(e);
5664
} catch (Throwable t) {
57-
taskCompletionSource.setException(wrapException(t));
65+
taskCompletionSource.setException(FirebaseAppDistributionException.wrap(t));
5866
}
5967
});
6068
return taskCompletionSource.getTask();
@@ -79,14 +87,61 @@ static <TResult> Task<TResult> handleTaskFailure(Task<TResult> task) {
7987
LogWrapper.getInstance().e(TAG + "Task failed to complete due to " + e.getMessage(), e);
8088
return e instanceof FirebaseAppDistributionException
8189
? task
82-
: Tasks.forException(wrapException(e));
90+
: Tasks.forException(FirebaseAppDistributionException.wrap(e));
8391
}
8492
return task;
8593
}
8694

87-
private static FirebaseAppDistributionException wrapException(Throwable t) {
88-
return new FirebaseAppDistributionException(
89-
String.format("%s: %s", ErrorMessages.UNKNOWN_ERROR, t.getMessage()), Status.UNKNOWN, t);
95+
/**
96+
* An @{link AutoValue} class to hold the result of two Tasks, combined using {@link
97+
* #combineWithResultOf}.
98+
*
99+
* @param <T1> The result type of the first task
100+
* @param <T2> The result type of the second task
101+
*/
102+
@AutoValue
103+
abstract static class CombinedTaskResults<T1, T2> {
104+
abstract T1 first();
105+
106+
abstract T2 second();
107+
108+
static <T1, T2> CombinedTaskResults<T1, T2> create(T1 first, T2 second) {
109+
return new AutoValue_TaskUtils_CombinedTaskResults(first, second);
110+
}
111+
}
112+
113+
/**
114+
* Returns a {@link SuccessContinuation} to be chained off of a {@link Task}, that will run
115+
* another task in sequence and combine both results together.
116+
*
117+
* <p>This is useful when you want to run two tasks and use the results of each, but those tasks
118+
* need to be run sequentially. If they can be run in parallel, use {@link Tasks#whenAll} or one
119+
* of its variations.
120+
*
121+
* <p>Usage:
122+
*
123+
* <pre>{@code
124+
* runFirstAsyncTask()
125+
* .onSuccessTask(combineWithResultOf(executor, () -> startSecondAsyncTask())
126+
* .addOnSuccessListener(
127+
* results ->
128+
* doSomethingWithBothResults(results.result1(), results.result2()));
129+
* }</pre>
130+
*
131+
* @param secondTaskSource A {@link TaskSource} providing the next task to run
132+
* @param <T1> The result type of the first task
133+
* @param <T2> The result type of the second task
134+
* @return A {@link SuccessContinuation} that will return a new task with result type {@link
135+
* CombinedTaskResults}, combining the results of both tasks
136+
*/
137+
static <T1, T2> SuccessContinuation<T1, CombinedTaskResults<T1, T2>> combineWithResultOf(
138+
TaskSource<T2> secondTaskSource) {
139+
return firstResult ->
140+
secondTaskSource
141+
.get()
142+
.onSuccessTask(
143+
secondResult ->
144+
Tasks.forResult(CombinedTaskResults.create(firstResult, secondResult)));
90145
}
91146

92147
static void safeSetTaskException(TaskCompletionSource taskCompletionSource, Exception e) {

0 commit comments

Comments
 (0)