21
21
import android .app .Application ;
22
22
import android .content .Context ;
23
23
import android .os .Bundle ;
24
+ import androidx .annotation .GuardedBy ;
24
25
import androidx .annotation .NonNull ;
25
26
import androidx .annotation .VisibleForTesting ;
26
27
import com .google .android .gms .common .internal .Preconditions ;
27
28
import com .google .android .gms .tasks .Task ;
28
- import com .google .android .gms .tasks .TaskCompletionSource ;
29
29
import com .google .android .gms .tasks .Tasks ;
30
30
import com .google .firebase .FirebaseApp ;
31
31
import com .google .firebase .appdistribution .internal .AppDistributionReleaseInternal ;
@@ -39,10 +39,15 @@ public class FirebaseAppDistribution implements Application.ActivityLifecycleCal
39
39
private final CheckForUpdateClient checkForUpdateClient ;
40
40
private final UpdateAppClient updateAppClient ;
41
41
private Activity currentActivity ;
42
+ private static final int UNKNOWN_RELEASE_FILE_SIZE = -1 ;
42
43
43
- private Task <Void > cachedUpdateToLatestReleaseTask ;
44
+ @ GuardedBy ("updateTaskLock" )
45
+ private UpdateTaskImpl cachedUpdateIfNewReleaseTask ;
46
+
47
+ private final Object updateTaskLock = new Object ();
44
48
private Task <AppDistributionRelease > cachedCheckForUpdateTask ;
45
- private AppDistributionReleaseInternal cachedLatestRelease ;
49
+
50
+ private AppDistributionReleaseInternal cachedNewRelease ;
46
51
private final SignInStorage signInStorage ;
47
52
48
53
/** Constructor for FirebaseAppDistribution */
@@ -103,30 +108,54 @@ public static FirebaseAppDistribution getInstance(@NonNull FirebaseApp app) {
103
108
}
104
109
105
110
/**
106
- * Updates the app to the latest release, if one is available. Returns the release information or
111
+ * Updates the app to the newest release, if one is available. Returns the release information or
107
112
* null if no update is found. Performs the following actions: 1. If tester is not signed in,
108
113
* presents the tester with a Google sign in UI 2. Checks if a newer release is available. If so,
109
114
* presents the tester with a confirmation dialog to begin the download. 3. For APKs, downloads
110
115
* the binary and starts an installation intent. 4. For AABs, directs the tester to the Play app
111
116
* to complete the download and installation.
112
117
*/
113
118
@ NonNull
114
- public synchronized Task <Void > updateToLatestRelease () {
115
- if (cachedUpdateToLatestReleaseTask != null && !cachedUpdateToLatestReleaseTask .isComplete ()) {
116
- return cachedUpdateToLatestReleaseTask ;
117
- }
118
-
119
- cachedUpdateToLatestReleaseTask =
120
- checkForUpdate ()
121
- .onSuccessTask (
122
- release -> {
123
- if (release == null ) {
124
- return Tasks .forResult (null );
125
- }
126
- return showUpdateAlertDialog (release );
127
- });
119
+ public synchronized UpdateTask updateIfNewReleaseAvailable () {
120
+ synchronized (updateTaskLock ) {
121
+ if (cachedUpdateIfNewReleaseTask != null && !cachedUpdateIfNewReleaseTask .isComplete ()) {
122
+ return cachedUpdateIfNewReleaseTask ;
123
+ }
128
124
129
- return cachedUpdateToLatestReleaseTask ;
125
+ cachedUpdateIfNewReleaseTask = new UpdateTaskImpl ();
126
+ }
127
+ checkForNewRelease ()
128
+ .onSuccessTask (
129
+ release -> {
130
+ if (release == null ) {
131
+ postProgressToCachedUpdateIfNewReleaseTask (
132
+ UpdateProgress .builder ()
133
+ .setApkFileTotalBytes (UNKNOWN_RELEASE_FILE_SIZE )
134
+ .setApkBytesDownloaded (UNKNOWN_RELEASE_FILE_SIZE )
135
+ .setUpdateStatus (UpdateStatus .NEW_RELEASE_NOT_AVAILABLE )
136
+ .build ());
137
+ setCachedUpdateIfNewReleaseResult ();
138
+ return Tasks .forResult (null );
139
+ }
140
+ return showUpdateAlertDialog (release );
141
+ })
142
+ .addOnFailureListener (
143
+ e -> {
144
+ postProgressToCachedUpdateIfNewReleaseTask (
145
+ UpdateProgress .builder ()
146
+ .setApkFileTotalBytes (UNKNOWN_RELEASE_FILE_SIZE )
147
+ .setApkBytesDownloaded (UNKNOWN_RELEASE_FILE_SIZE )
148
+ .setUpdateStatus (UpdateStatus .NEW_RELEASE_CHECK_FAILED )
149
+ .build ());
150
+ setCachedUpdateIfNewReleaseCompletionError (
151
+ e ,
152
+ new FirebaseAppDistributionException (
153
+ Constants .ErrorMessages .NETWORK_ERROR ,
154
+ FirebaseAppDistributionException .Status .NETWORK_FAILURE ));
155
+ });
156
+ synchronized (updateTaskLock ) {
157
+ return cachedUpdateIfNewReleaseTask ;
158
+ }
130
159
}
131
160
132
161
/** Signs in the App Distribution tester. Presents the tester with a Google sign in UI */
@@ -141,18 +170,17 @@ public Task<Void> signInTester() {
141
170
* sign in UI
142
171
*/
143
172
@ NonNull
144
- public synchronized Task <AppDistributionRelease > checkForUpdate () {
173
+ public synchronized Task <AppDistributionRelease > checkForNewRelease () {
145
174
if (cachedCheckForUpdateTask != null && !cachedCheckForUpdateTask .isComplete ()) {
146
175
LogWrapper .getInstance ().v ("Response in progress" );
147
176
return cachedCheckForUpdateTask ;
148
177
}
149
-
150
178
cachedCheckForUpdateTask =
151
179
signInTester ()
152
180
.onSuccessTask (unused -> this .checkForUpdateClient .checkForUpdate ())
153
181
.onSuccessTask (
154
182
appDistributionReleaseInternal -> {
155
- setCachedLatestRelease (appDistributionReleaseInternal );
183
+ setCachedNewRelease (appDistributionReleaseInternal );
156
184
return Tasks .forResult (
157
185
ReleaseUtils .convertToAppDistributionRelease (appDistributionReleaseInternal ));
158
186
});
@@ -161,11 +189,11 @@ public synchronized Task<AppDistributionRelease> checkForUpdate() {
161
189
}
162
190
163
191
/**
164
- * Updates app to the latest release. If the latest release is an APK, downloads the binary and
165
- * starts an installation If the latest release is an AAB, directs the tester to the Play app to
192
+ * Updates app to the newest release. If the newest release is an APK, downloads the binary and
193
+ * starts an installation If the newest release is an AAB, directs the tester to the Play app to
166
194
* complete the download and installation.
167
195
*
168
- * <p>cancels task with FirebaseAppDistributionException with UPDATE_NOT_AVAIALBLE exception if no
196
+ * <p>cancels task with FirebaseAppDistributionException with UPDATE_NOT_AVAILABLE exception if no
169
197
* new release is cached from checkForUpdate
170
198
*/
171
199
@ NonNull
@@ -186,7 +214,7 @@ private synchronized UpdateTask updateApp(boolean showDownloadInNotificationMana
186
214
return updateTask ;
187
215
}
188
216
189
- return this .updateAppClient .updateApp (cachedLatestRelease , showDownloadInNotificationManager );
217
+ return this .updateAppClient .updateApp (cachedNewRelease , showDownloadInNotificationManager );
190
218
}
191
219
192
220
/** Returns true if the App Distribution tester is signed in */
@@ -196,7 +224,7 @@ public boolean isTesterSignedIn() {
196
224
197
225
/** Signs out the App Distribution tester */
198
226
public void signOutTester () {
199
- this .cachedLatestRelease = null ;
227
+ this .cachedNewRelease = null ;
200
228
this .signInStorage .setSignInStatus (false );
201
229
}
202
230
@@ -266,17 +294,16 @@ public void onActivityDestroyed(@NonNull Activity activity) {
266
294
}
267
295
268
296
@ VisibleForTesting
269
- void setCachedLatestRelease (AppDistributionReleaseInternal latestRelease ) {
270
- this .cachedLatestRelease = latestRelease ;
297
+ void setCachedNewRelease (AppDistributionReleaseInternal newRelease ) {
298
+ this .cachedNewRelease = newRelease ;
271
299
}
272
300
273
301
@ VisibleForTesting
274
- AppDistributionReleaseInternal getCachedLatestRelease () {
275
- return this .cachedLatestRelease ;
302
+ AppDistributionReleaseInternal getCachedNewRelease () {
303
+ return this .cachedNewRelease ;
276
304
}
277
305
278
- private Task <Void > showUpdateAlertDialog (AppDistributionRelease latestRelease ) {
279
- TaskCompletionSource <Void > updateAlertDialogTask = new TaskCompletionSource <>();
306
+ private UpdateTaskImpl showUpdateAlertDialog (AppDistributionRelease newRelease ) {
280
307
Context context = firebaseApp .getApplicationContext ();
281
308
AlertDialog alertDialog = new AlertDialog .Builder (currentActivity ).create ();
282
309
alertDialog .setTitle (context .getString (R .string .update_dialog_title ));
@@ -285,38 +312,82 @@ private Task<Void> showUpdateAlertDialog(AppDistributionRelease latestRelease) {
285
312
new StringBuilder (
286
313
String .format (
287
314
"Version %s (%s) is available." ,
288
- latestRelease .getDisplayVersion (), latestRelease .getVersionCode ()));
315
+ newRelease .getDisplayVersion (), newRelease .getVersionCode ()));
289
316
290
- if (latestRelease .getReleaseNotes () != null && !latestRelease .getReleaseNotes ().isEmpty ()) {
291
- message .append (String .format ("\n \n Release notes: %s" , latestRelease .getReleaseNotes ()));
317
+ if (newRelease .getReleaseNotes () != null && !newRelease .getReleaseNotes ().isEmpty ()) {
318
+ message .append (String .format ("\n \n Release notes: %s" , newRelease .getReleaseNotes ()));
292
319
}
293
320
294
321
alertDialog .setMessage (message );
295
322
alertDialog .setButton (
296
323
AlertDialog .BUTTON_POSITIVE ,
297
324
context .getString (R .string .update_yes_button ),
298
- (dialogInterface , i ) ->
325
+ (dialogInterface , i ) -> {
326
+ synchronized (updateTaskLock ) {
299
327
// show download progress in notification manager
300
328
updateApp (true )
301
- .addOnSuccessListener (unused -> updateAlertDialogTask .setResult (null ))
302
- .addOnFailureListener (updateAlertDialogTask ::setException ));
329
+ .addOnProgressListener (
330
+ progress -> postProgressToCachedUpdateIfNewReleaseTask (progress ))
331
+ .addOnSuccessListener (unused -> setCachedUpdateIfNewReleaseResult ())
332
+ .addOnFailureListener (cachedUpdateIfNewReleaseTask ::setException );
333
+ }
334
+ });
303
335
304
336
alertDialog .setButton (
305
337
AlertDialog .BUTTON_NEGATIVE ,
306
338
context .getString (R .string .update_no_button ),
307
339
(dialogInterface , i ) -> {
308
340
dialogInterface .dismiss ();
309
- updateAlertDialogTask .setException (
310
- new FirebaseAppDistributionException (
311
- Constants .ErrorMessages .UPDATE_CANCELED ,
312
- FirebaseAppDistributionException .Status .INSTALLATION_CANCELED ));
341
+ synchronized (updateTaskLock ) {
342
+ postProgressToCachedUpdateIfNewReleaseTask (
343
+ UpdateProgress .builder ()
344
+ .setApkFileTotalBytes (UNKNOWN_RELEASE_FILE_SIZE )
345
+ .setApkBytesDownloaded (UNKNOWN_RELEASE_FILE_SIZE )
346
+ .setUpdateStatus (UpdateStatus .UPDATE_CANCELED )
347
+ .build ());
348
+ setCachedUpdateIfNewReleaseCompletionError (
349
+ new FirebaseAppDistributionException (
350
+ Constants .ErrorMessages .UPDATE_CANCELED ,
351
+ FirebaseAppDistributionException .Status .INSTALLATION_CANCELED ));
352
+ }
313
353
});
314
354
315
355
alertDialog .show ();
316
- return updateAlertDialogTask .getTask ();
356
+ synchronized (updateTaskLock ) {
357
+ return cachedUpdateIfNewReleaseTask ;
358
+ }
317
359
}
318
360
319
361
void setInstallationResult (int resultCode ) {
320
362
this .updateAppClient .setInstallationResult (resultCode );
321
363
}
364
+
365
+ private void setCachedUpdateIfNewReleaseCompletionError (FirebaseAppDistributionException e ) {
366
+ synchronized (updateTaskLock ) {
367
+ if (cachedUpdateIfNewReleaseTask != null && !cachedUpdateIfNewReleaseTask .isComplete ()) {
368
+ cachedUpdateIfNewReleaseTask .setException (e );
369
+ }
370
+ }
371
+ }
372
+
373
+ private void setCachedUpdateIfNewReleaseCompletionError (
374
+ Exception e , FirebaseAppDistributionException defaultFirebaseException ) {
375
+ if (e instanceof FirebaseAppDistributionException ) {
376
+ setCachedUpdateIfNewReleaseCompletionError ((FirebaseAppDistributionException ) e );
377
+ } else {
378
+ setCachedUpdateIfNewReleaseCompletionError (defaultFirebaseException );
379
+ }
380
+ }
381
+
382
+ private void postProgressToCachedUpdateIfNewReleaseTask (UpdateProgress progress ) {
383
+ synchronized (updateTaskLock ) {
384
+ cachedUpdateIfNewReleaseTask .updateProgress (progress );
385
+ }
386
+ }
387
+
388
+ private void setCachedUpdateIfNewReleaseResult () {
389
+ synchronized (updateTaskLock ) {
390
+ cachedUpdateIfNewReleaseTask .setResult ();
391
+ }
392
+ }
322
393
}
0 commit comments