Skip to content

Commit d701761

Browse files
Fix update app synchronization issues (#2906)
* Fix update app synchronization issues * Fixing synchronization issues * Fixing synchronization * Implement AAB updateTask completion on app resume (#2905) * Implement AAB updateTask completion on app resume * fixing build issues
1 parent 7cce853 commit d701761

File tree

9 files changed

+232
-143
lines changed

9 files changed

+232
-143
lines changed

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

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ public class FirebaseAppDistribution implements Application.ActivityLifecycleCal
4444

4545
private Task<Void> cachedUpdateToLatestReleaseTask;
4646
private Task<AppDistributionRelease> cachedCheckForUpdateTask;
47-
private UpdateTaskImpl cachedUpdateAppTask;
4847
private AppDistributionReleaseInternal cachedLatestRelease;
4948
private final SignInStorage signInStorage;
5049

@@ -156,7 +155,7 @@ public synchronized Task<AppDistributionRelease> checkForUpdate() {
156155
appDistributionReleaseInternal -> {
157156
setCachedLatestRelease(appDistributionReleaseInternal);
158157
return Tasks.forResult(
159-
convertToAppDistributionRelease(appDistributionReleaseInternal));
158+
ReleaseUtils.convertToAppDistributionRelease(appDistributionReleaseInternal));
160159
});
161160

162161
return cachedCheckForUpdateTask;
@@ -220,6 +219,9 @@ public void onActivityStarted(@NonNull Activity activity) {
220219
@Override
221220
public void onActivityResumed(@NonNull Activity activity) {
222221
Log.d(TAG, "Resumed activity: " + activity.getClass().getName());
222+
// If app resumes and aab update task is in progress, assume that installation didn't happen so
223+
// cancel the task
224+
updateAppClient.tryCancelAabUpdateTask();
223225

224226
// SignInResultActivity is only opened after successful redirection from signIn flow,
225227
// should not be treated as reentering the app
@@ -262,25 +264,6 @@ public void onActivityDestroyed(@NonNull Activity activity) {
262264
}
263265
}
264266

265-
private AppDistributionRelease convertToAppDistributionRelease(
266-
AppDistributionReleaseInternal internalRelease) {
267-
if (internalRelease == null) {
268-
return null;
269-
}
270-
long versionCode;
271-
try {
272-
versionCode = Long.parseLong(internalRelease.getBuildVersion());
273-
} catch (NumberFormatException e) {
274-
versionCode = 0;
275-
}
276-
return AppDistributionRelease.builder()
277-
.setVersionCode(versionCode)
278-
.setDisplayVersion(internalRelease.getDisplayVersion())
279-
.setReleaseNotes(internalRelease.getReleaseNotes())
280-
.setBinaryType(internalRelease.getBinaryType())
281-
.build();
282-
}
283-
284267
@VisibleForTesting
285268
void setCachedLatestRelease(AppDistributionReleaseInternal latestRelease) {
286269
this.cachedLatestRelease = latestRelease;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public FirebaseAppDistributionException(
8989
}
9090

9191
/** Get cached release when error was thrown */
92-
@NonNull
92+
@Nullable
9393
public AppDistributionRelease getRelease() {
9494
return release;
9595
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.appdistribution;
16+
17+
import com.google.firebase.appdistribution.internal.AppDistributionReleaseInternal;
18+
19+
class ReleaseUtils {
20+
static AppDistributionRelease convertToAppDistributionRelease(
21+
AppDistributionReleaseInternal internalRelease) {
22+
if (internalRelease == null) {
23+
return null;
24+
}
25+
long versionCode;
26+
try {
27+
versionCode = Long.parseLong(internalRelease.getBuildVersion());
28+
} catch (NumberFormatException e) {
29+
versionCode = 0;
30+
}
31+
return AppDistributionRelease.builder()
32+
.setVersionCode(versionCode)
33+
.setDisplayVersion(internalRelease.getDisplayVersion())
34+
.setReleaseNotes(internalRelease.getReleaseNotes())
35+
.setBinaryType(internalRelease.getBinaryType())
36+
.build();
37+
}
38+
}

firebase-app-distribution/src/main/java/com/google/firebase/appdistribution/UpdateApkClient.java

Lines changed: 79 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import androidx.annotation.GuardedBy;
2525
import androidx.annotation.NonNull;
2626
import androidx.annotation.Nullable;
27-
import androidx.annotation.RestrictTo;
2827
import androidx.annotation.VisibleForTesting;
2928
import com.google.android.gms.tasks.CancellationTokenSource;
3029
import com.google.android.gms.tasks.Task;
@@ -43,8 +42,7 @@
4342

4443
/** Client class that handles updateApp functionality for APKs in {@link UpdateAppClient}. */
4544
class UpdateApkClient {
46-
47-
private final int UPDATE_INTERVAL_MS = 250;
45+
private static final int UPDATE_INTERVAL_MS = 250;
4846
private static final String TAG = "FADUpdateAppClient";
4947
private static final String REQUEST_METHOD = "GET";
5048
private final FirebaseAppDistributionNotificationsManager appDistributionNotificationsManager;
@@ -53,13 +51,15 @@ class UpdateApkClient {
5351
private final Executor downloadExecutor;
5452
private TaskCompletionSource<Void> installTaskCompletionSource;
5553
private final FirebaseApp firebaseApp;
54+
55+
@GuardedBy("updateTaskLock")
5656
private UpdateTaskImpl cachedUpdateTask;
57-
private boolean showDownloadInNotificationManager = false;
5857

5958
@GuardedBy("activityLock")
6059
private Activity currentActivity;
6160

6261
private final Object activityLock = new Object();
62+
private final Object updateTaskLock = new Object();
6363

6464
public UpdateApkClient(@NonNull FirebaseApp firebaseApp) {
6565
this.downloadExecutor = Executors.newSingleThreadExecutor();
@@ -68,20 +68,25 @@ public UpdateApkClient(@NonNull FirebaseApp firebaseApp) {
6868
new FirebaseAppDistributionNotificationsManager(firebaseApp);
6969
}
7070

71-
public void updateApk(
72-
@NonNull UpdateTaskImpl updateTask,
73-
@NonNull String downloadUrl,
74-
@NonNull boolean showDownloadInNotificationManager) {
75-
this.showDownloadInNotificationManager = showDownloadInNotificationManager;
76-
this.cachedUpdateTask = updateTask;
77-
downloadApk(downloadUrl)
71+
public synchronized UpdateTaskImpl updateApk(
72+
@NonNull String downloadUrl, boolean showDownloadNotificationManager) {
73+
synchronized (updateTaskLock) {
74+
if (cachedUpdateTask != null && !cachedUpdateTask.isComplete()) {
75+
return cachedUpdateTask;
76+
}
77+
78+
cachedUpdateTask = new UpdateTaskImpl();
79+
}
80+
81+
downloadApk(downloadUrl, showDownloadNotificationManager)
7882
.addOnSuccessListener(
7983
downloadExecutor,
8084
file ->
8185
install(file.getPath())
8286
.addOnFailureListener(
8387
e -> {
84-
postInstallationFailure(e, file.length());
88+
postInstallationFailure(
89+
e, file.length(), showDownloadNotificationManager);
8590
setTaskCompletionErrorWithDefault(
8691
e,
8792
new FirebaseAppDistributionException(
@@ -97,23 +102,28 @@ public void updateApk(
97102
Constants.ErrorMessages.NETWORK_ERROR,
98103
FirebaseAppDistributionException.Status.DOWNLOAD_FAILURE));
99104
});
105+
106+
synchronized (updateTaskLock) {
107+
return cachedUpdateTask;
108+
}
100109
}
101110

102111
@VisibleForTesting
103112
@NonNull
104-
Task<File> downloadApk(@NonNull String downloadUrl) {
113+
Task<File> downloadApk(@NonNull String downloadUrl, boolean showDownloadNotificationManager) {
105114
if (downloadTaskCompletionSource != null
106115
&& !downloadTaskCompletionSource.getTask().isComplete()) {
107116
return downloadTaskCompletionSource.getTask();
108117
}
109118

110119
downloadTaskCompletionSource = new TaskCompletionSource<>();
111120

112-
makeApkDownloadRequest(downloadUrl);
121+
makeApkDownloadRequest(downloadUrl, showDownloadNotificationManager);
113122
return downloadTaskCompletionSource.getTask();
114123
}
115124

116-
private void makeApkDownloadRequest(@NonNull String downloadUrl) {
125+
private void makeApkDownloadRequest(
126+
@NonNull String downloadUrl, boolean showDownloadNotificationManager) {
117127
downloadExecutor.execute(
118128
() -> {
119129
try {
@@ -126,9 +136,14 @@ private void makeApkDownloadRequest(@NonNull String downloadUrl) {
126136
FirebaseAppDistributionException.Status.DOWNLOAD_FAILURE));
127137
} else {
128138
long responseLength = connection.getContentLength();
129-
postUpdateProgress(responseLength, 0, UpdateStatus.PENDING);
139+
postUpdateProgress(
140+
responseLength, 0, UpdateStatus.PENDING, showDownloadNotificationManager);
130141
String fileName = getApplicationName() + ".apk";
131-
downloadToDisk(connection.getInputStream(), responseLength, fileName);
142+
downloadToDisk(
143+
connection.getInputStream(),
144+
responseLength,
145+
fileName,
146+
showDownloadNotificationManager);
132147
}
133148
} catch (IOException | FirebaseAppDistributionException e) {
134149
setDownloadTaskCompletionErrorWithDefault(
@@ -140,7 +155,8 @@ private void makeApkDownloadRequest(@NonNull String downloadUrl) {
140155
});
141156
}
142157

143-
private void downloadToDisk(InputStream input, long totalSize, String fileName) {
158+
private void downloadToDisk(
159+
InputStream input, long totalSize, String fileName, boolean showDownloadNotificationManager) {
144160

145161
File apkFile = getApkFileForApp(fileName);
146162
apkFile.delete();
@@ -162,12 +178,20 @@ private void downloadToDisk(InputStream input, long totalSize, String fileName)
162178
long currentTimeMs = System.currentTimeMillis();
163179
if (currentTimeMs - lastMsUpdated > UPDATE_INTERVAL_MS) {
164180
lastMsUpdated = currentTimeMs;
165-
postUpdateProgress(totalSize, bytesDownloaded, UpdateStatus.DOWNLOADING);
181+
postUpdateProgress(
182+
totalSize,
183+
bytesDownloaded,
184+
UpdateStatus.DOWNLOADING,
185+
showDownloadNotificationManager);
166186
}
167187
}
168188

169189
} catch (IOException e) {
170-
postUpdateProgress(totalSize, bytesDownloaded, UpdateStatus.DOWNLOAD_FAILED);
190+
postUpdateProgress(
191+
totalSize,
192+
bytesDownloaded,
193+
UpdateStatus.DOWNLOAD_FAILED,
194+
showDownloadNotificationManager);
171195
setDownloadTaskCompletionError(
172196
new FirebaseAppDistributionException(
173197
Constants.ErrorMessages.NETWORK_ERROR,
@@ -178,15 +202,20 @@ private void downloadToDisk(InputStream input, long totalSize, String fileName)
178202
// check that file is actual JAR
179203
new JarFile(apkFile);
180204
} catch (Exception e) {
181-
postUpdateProgress(totalSize, bytesDownloaded, UpdateStatus.DOWNLOAD_FAILED);
205+
postUpdateProgress(
206+
totalSize,
207+
bytesDownloaded,
208+
UpdateStatus.DOWNLOAD_FAILED,
209+
showDownloadNotificationManager);
182210
setDownloadTaskCompletionError(
183211
new FirebaseAppDistributionException(
184212
Constants.ErrorMessages.NETWORK_ERROR,
185213
FirebaseAppDistributionException.Status.DOWNLOAD_FAILURE));
186214
}
187215

188216
// completion
189-
postUpdateProgress(totalSize, totalSize, UpdateStatus.DOWNLOADED);
217+
postUpdateProgress(
218+
totalSize, totalSize, UpdateStatus.DOWNLOADED, showDownloadNotificationManager);
190219

191220
downloadTaskCompletionSource.setResult(apkFile);
192221
}
@@ -257,7 +286,10 @@ private Task<Void> install(String path) {
257286
void setInstallationResult(int resultCode) {
258287
if (resultCode == Activity.RESULT_OK) {
259288
installTaskCompletionSource.setResult(null);
260-
cachedUpdateTask.setResult();
289+
290+
synchronized (updateTaskLock) {
291+
cachedUpdateTask.setResult();
292+
}
261293
} else if (resultCode == Activity.RESULT_CANCELED) {
262294
installTaskCompletionSource.setException(
263295
new FirebaseAppDistributionException(
@@ -272,8 +304,10 @@ void setInstallationResult(int resultCode) {
272304
}
273305

274306
private void setTaskCompletionError(FirebaseAppDistributionException e) {
275-
if (cachedUpdateTask != null && !cachedUpdateTask.isComplete()) {
276-
cachedUpdateTask.setException(e);
307+
synchronized (updateTaskLock) {
308+
if (cachedUpdateTask != null && !cachedUpdateTask.isComplete()) {
309+
cachedUpdateTask.setException(e);
310+
}
277311
}
278312
}
279313

@@ -287,24 +321,33 @@ private void setTaskCompletionErrorWithDefault(
287321
}
288322

289323
@VisibleForTesting
290-
void postUpdateProgress(long totalBytes, long downloadedBytes, UpdateStatus status) {
291-
cachedUpdateTask.updateProgress(
292-
UpdateProgress.builder()
293-
.setApkFileTotalBytes(totalBytes)
294-
.setApkBytesDownloaded(downloadedBytes)
295-
.setUpdateStatus(status)
296-
.build());
297-
if (showDownloadInNotificationManager) {
324+
void postUpdateProgress(
325+
long totalBytes,
326+
long downloadedBytes,
327+
UpdateStatus status,
328+
boolean showDownloadNotificationManager) {
329+
synchronized (updateTaskLock) {
330+
cachedUpdateTask.updateProgress(
331+
UpdateProgress.builder()
332+
.setApkFileTotalBytes(totalBytes)
333+
.setApkBytesDownloaded(downloadedBytes)
334+
.setUpdateStatus(status)
335+
.build());
336+
}
337+
if (showDownloadNotificationManager) {
298338
appDistributionNotificationsManager.updateNotification(totalBytes, downloadedBytes, status);
299339
}
300340
}
301341

302-
private void postInstallationFailure(Exception e, long fileLength) {
342+
private void postInstallationFailure(
343+
Exception e, long fileLength, boolean showDownloadNotificationManager) {
303344
if (e instanceof FirebaseAppDistributionException
304345
&& ((FirebaseAppDistributionException) e).getErrorCode() == Status.INSTALLATION_CANCELED) {
305-
postUpdateProgress(fileLength, fileLength, UpdateStatus.INSTALL_CANCELED);
346+
postUpdateProgress(
347+
fileLength, fileLength, UpdateStatus.INSTALL_CANCELED, showDownloadNotificationManager);
306348
} else {
307-
postUpdateProgress(fileLength, fileLength, UpdateStatus.INSTALL_FAILED);
349+
postUpdateProgress(
350+
fileLength, fileLength, UpdateStatus.INSTALL_FAILED, showDownloadNotificationManager);
308351
}
309352
}
310353

@@ -320,9 +363,4 @@ void setCurrentActivity(@Nullable Activity activity) {
320363
this.currentActivity = activity;
321364
}
322365
}
323-
324-
@RestrictTo(RestrictTo.Scope.TESTS)
325-
void setCachedUpdateTask(UpdateTaskImpl updateTask) {
326-
this.cachedUpdateTask = updateTask;
327-
}
328366
}

0 commit comments

Comments
 (0)