Skip to content

Commit 3720a1b

Browse files
committed
Clean up task management in NewReleaseFetcher
1 parent 444edc1 commit 3720a1b

File tree

3 files changed

+144
-128
lines changed

3 files changed

+144
-128
lines changed

firebase-app-distribution/src/main/java/com/google/firebase/app/distribution/NewReleaseFetcher.java

Lines changed: 82 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,20 @@
1515
package com.google.firebase.app.distribution;
1616

1717
import static com.google.firebase.app.distribution.ReleaseIdentificationUtils.calculateApkHash;
18+
import static com.google.firebase.app.distribution.ReleaseIdentificationUtils.getPackageInfo;
19+
import static com.google.firebase.app.distribution.ReleaseIdentificationUtils.getPackageInfoWithMetadata;
20+
import static com.google.firebase.app.distribution.TaskUtils.runAsyncInTask;
1821

1922
import android.content.Context;
2023
import android.content.pm.PackageInfo;
21-
import android.content.pm.PackageManager;
2224
import androidx.annotation.NonNull;
25+
import androidx.annotation.Nullable;
2326
import androidx.annotation.VisibleForTesting;
2427
import androidx.core.content.pm.PackageInfoCompat;
2528
import com.google.android.gms.tasks.Task;
2629
import com.google.android.gms.tasks.Tasks;
2730
import com.google.firebase.FirebaseApp;
31+
import com.google.firebase.app.distribution.FirebaseAppDistributionException.Status;
2832
import com.google.firebase.app.distribution.internal.LogWrapper;
2933
import com.google.firebase.inject.Provider;
3034
import com.google.firebase.installations.FirebaseInstallationsApi;
@@ -88,81 +92,70 @@ public synchronized Task<AppDistributionReleaseInternal> checkForNewRelease() {
8892
this.cachedCheckForNewRelease =
8993
Tasks.whenAllSuccess(installationIdTask, installationAuthTokenTask)
9094
.onSuccessTask(
91-
taskExecutor,
92-
tasks -> {
93-
String fid = installationIdTask.getResult();
94-
InstallationTokenResult installationTokenResult =
95-
installationAuthTokenTask.getResult();
96-
try {
97-
AppDistributionReleaseInternal newRelease =
98-
getNewReleaseFromClient(
99-
fid,
100-
firebaseApp.getOptions().getApplicationId(),
101-
firebaseApp.getOptions().getApiKey(),
102-
installationTokenResult.getToken());
103-
return Tasks.forResult(newRelease);
104-
} catch (FirebaseAppDistributionException ex) {
105-
return Tasks.forException(ex);
106-
}
107-
})
108-
.continueWithTask(
109-
taskExecutor,
110-
task ->
111-
TaskUtils.handleTaskFailure(
112-
task,
113-
Constants.ErrorMessages.NETWORK_ERROR,
114-
FirebaseAppDistributionException.Status.NETWORK_FAILURE));
95+
unused ->
96+
getNewReleaseFromClientTask(
97+
installationIdTask.getResult(),
98+
firebaseApp.getOptions().getApplicationId(),
99+
firebaseApp.getOptions().getApiKey(),
100+
installationAuthTokenTask.getResult().getToken()));
115101

116102
return cachedCheckForNewRelease;
117103
}
118104

105+
private Task<AppDistributionReleaseInternal> getNewReleaseFromClientTask(
106+
String fid, String appId, String apiKey, String authToken) {
107+
return runAsyncInTask(
108+
taskExecutor, () -> getNewReleaseFromClient(fid, appId, apiKey, authToken));
109+
}
110+
111+
@Nullable
119112
@VisibleForTesting
120113
AppDistributionReleaseInternal getNewReleaseFromClient(
121114
String fid, String appId, String apiKey, String authToken)
122115
throws FirebaseAppDistributionException {
116+
AppDistributionReleaseInternal retrievedNewRelease =
117+
firebaseAppDistributionTesterApiClient.fetchNewRelease(
118+
fid, appId, apiKey, authToken, firebaseApp.getApplicationContext());
119+
120+
if (retrievedNewRelease == null) {
121+
LogWrapper.getInstance().v(TAG + "Tester does not have access to any releases");
122+
return null;
123+
}
124+
125+
long newReleaseBuildVersion = parseBuildVersion(retrievedNewRelease.getBuildVersion());
126+
127+
if (isOlderBuildVersion(newReleaseBuildVersion)) {
128+
LogWrapper.getInstance().v(TAG + "New release has lower version code than current release");
129+
return null;
130+
}
131+
132+
if (isNewerBuildVersion(newReleaseBuildVersion)
133+
|| !isSameAsInstalledRelease(retrievedNewRelease)
134+
|| hasDifferentAppVersionName(retrievedNewRelease)) {
135+
return retrievedNewRelease;
136+
} else {
137+
LogWrapper.getInstance().v(TAG + "New release is older or is currently installed");
138+
return null;
139+
}
140+
}
141+
142+
private long parseBuildVersion(String buildVersion) throws FirebaseAppDistributionException {
123143
try {
124-
AppDistributionReleaseInternal retrievedNewRelease =
125-
firebaseAppDistributionTesterApiClient.fetchNewRelease(
126-
fid, appId, apiKey, authToken, firebaseApp.getApplicationContext());
127-
128-
if (retrievedNewRelease == null) {
129-
LogWrapper.getInstance().v(TAG + "Tester does not have access to any releases");
130-
return null;
131-
}
132-
133-
if (!canInstall(retrievedNewRelease)) {
134-
LogWrapper.getInstance().v(TAG + "New release has lower version code than current release");
135-
return null;
136-
}
137-
138-
if (isNewerBuildVersion(retrievedNewRelease)
139-
|| !isSameAsInstalledRelease(retrievedNewRelease)
140-
|| hasDifferentAppVersionName(retrievedNewRelease)) {
141-
return retrievedNewRelease;
142-
} else {
143-
// Return null if retrieved new release is older or currently installed
144-
LogWrapper.getInstance().v(TAG + "New release is older or is currently installed");
145-
return null;
146-
}
144+
return Long.parseLong(buildVersion);
147145
} catch (NumberFormatException e) {
148-
LogWrapper.getInstance().e(TAG + "Error parsing buildVersion.", e);
149146
throw new FirebaseAppDistributionException(
150-
Constants.ErrorMessages.NETWORK_ERROR,
151-
FirebaseAppDistributionException.Status.NETWORK_FAILURE,
152-
e);
147+
"Could not parse build version of new release: " + buildVersion, Status.UNKNOWN, e);
153148
}
154149
}
155150

156-
private boolean canInstall(AppDistributionReleaseInternal newRelease)
151+
private boolean isOlderBuildVersion(long newReleaseBuildVersion)
157152
throws FirebaseAppDistributionException {
158-
return Long.parseLong(newRelease.getBuildVersion())
159-
>= getInstalledAppVersionCode(firebaseApp.getApplicationContext());
153+
return newReleaseBuildVersion < getInstalledAppVersionCode(firebaseApp.getApplicationContext());
160154
}
161155

162-
private boolean isNewerBuildVersion(AppDistributionReleaseInternal newRelease)
156+
private boolean isNewerBuildVersion(long newReleaseBuildVersion)
163157
throws FirebaseAppDistributionException {
164-
return Long.parseLong(newRelease.getBuildVersion())
165-
> getInstalledAppVersionCode(firebaseApp.getApplicationContext());
158+
return newReleaseBuildVersion > getInstalledAppVersionCode(firebaseApp.getApplicationContext());
166159
}
167160

168161
private boolean hasDifferentAppVersionName(AppDistributionReleaseInternal newRelease)
@@ -179,17 +172,26 @@ boolean isSameAsInstalledRelease(AppDistributionReleaseInternal newRelease)
179172
return hasSameHashAsInstalledRelease(newRelease);
180173
}
181174

182-
// TODO(lkellogg): getIasArtifactId() will likely never be null since it's set to the empty
183-
// string if not present in the response
184-
if (newRelease.getIasArtifactId() == null) {
175+
if (newRelease.getIasArtifactId() == null || newRelease.getIasArtifactId().isEmpty()) {
176+
LogWrapper.getInstance()
177+
.w(TAG + "AAB release missing IAS Artifact ID. Assuming new release is different.");
185178
return false;
186179
}
187-
// AAB BinaryType
188-
return newRelease
189-
.getIasArtifactId()
190-
.equals(
191-
ReleaseIdentificationUtils.extractInternalAppSharingArtifactId(
192-
firebaseApp.getApplicationContext()));
180+
181+
String installedIasArtifactId;
182+
try {
183+
installedIasArtifactId =
184+
ReleaseIdentificationUtils.extractInternalAppSharingArtifactId(
185+
firebaseApp.getApplicationContext());
186+
} catch (FirebaseAppDistributionException e) {
187+
LogWrapper.getInstance()
188+
.w(
189+
TAG + "Could not get installed IAS artifact ID. Assuming new release is different.",
190+
e);
191+
return false;
192+
}
193+
194+
return newRelease.getIasArtifactId().equals(installedIasArtifactId);
193195
}
194196

195197
private long getInstalledAppVersionCode(Context context) throws FirebaseAppDistributionException {
@@ -201,19 +203,6 @@ private String getInstalledAppVersionName(Context context)
201203
return getPackageInfo(context).versionName;
202204
}
203205

204-
private PackageInfo getPackageInfo(Context context) throws FirebaseAppDistributionException {
205-
try {
206-
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
207-
} catch (PackageManager.NameNotFoundException e) {
208-
LogWrapper.getInstance()
209-
.e(TAG + "Unable to find package with name " + context.getPackageName(), e);
210-
throw new FirebaseAppDistributionException(
211-
Constants.ErrorMessages.UNKNOWN_ERROR,
212-
FirebaseAppDistributionException.Status.UNKNOWN,
213-
e);
214-
}
215-
}
216-
217206
@VisibleForTesting
218207
String extractApkHash(PackageInfo packageInfo) {
219208
File sourceFile = new File(packageInfo.applicationInfo.sourceDir);
@@ -229,25 +218,19 @@ String extractApkHash(PackageInfo packageInfo) {
229218

230219
private boolean hasSameHashAsInstalledRelease(AppDistributionReleaseInternal newRelease)
231220
throws FirebaseAppDistributionException {
232-
try {
233-
Context context = firebaseApp.getApplicationContext();
234-
PackageInfo metadataPackageInfo =
235-
context
236-
.getPackageManager()
237-
.getPackageInfo(context.getPackageName(), PackageManager.GET_META_DATA);
238-
String installedReleaseApkHash = extractApkHash(metadataPackageInfo);
239-
240-
if (installedReleaseApkHash.isEmpty() || newRelease.getApkHash().isEmpty()) {
241-
LogWrapper.getInstance().e(TAG + "Missing APK hash.");
242-
throw new FirebaseAppDistributionException(
243-
Constants.ErrorMessages.UNKNOWN_ERROR, FirebaseAppDistributionException.Status.UNKNOWN);
244-
}
245-
// If the hash of the zipped APK for the retrieved newRelease is equal to the stored hash
246-
// of the installed release, then they are the same release.
247-
return installedReleaseApkHash.equals(newRelease.getApkHash());
248-
} catch (PackageManager.NameNotFoundException e) {
249-
LogWrapper.getInstance().e(TAG + "Unable to locate App.", e);
250-
return false;
221+
Context context = firebaseApp.getApplicationContext();
222+
PackageInfo metadataPackageInfo = getPackageInfoWithMetadata(context);
223+
String installedReleaseApkHash = extractApkHash(metadataPackageInfo);
224+
225+
if (installedReleaseApkHash == null || installedReleaseApkHash.isEmpty()) {
226+
throw new FirebaseAppDistributionException(
227+
"Could not calculate hash of installed APK", Status.UNKNOWN);
228+
} else if (newRelease.getApkHash().isEmpty()) {
229+
throw new FirebaseAppDistributionException(
230+
"Missing APK hash from new release", Status.UNKNOWN);
251231
}
232+
// If the hash of the zipped APK for the retrieved newRelease is equal to the stored hash
233+
// of the installed release, then they are the same release.
234+
return installedReleaseApkHash.equals(newRelease.getApkHash());
252235
}
253236
}

firebase-app-distribution/src/main/java/com/google/firebase/app/distribution/ReleaseIdentificationUtils.java

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import android.content.pm.PackageManager;
2020
import androidx.annotation.NonNull;
2121
import androidx.annotation.Nullable;
22+
import com.google.firebase.app.distribution.FirebaseAppDistributionException.Status;
2223
import com.google.firebase.app.distribution.internal.LogWrapper;
2324
import java.io.File;
2425
import java.io.IOException;
@@ -33,22 +34,51 @@
3334
final class ReleaseIdentificationUtils {
3435
private static final String TAG = "ReleaseIdentification";
3536
private static final int BYTES_IN_LONG = 8;
37+
private static final int NO_FLAGS = 0;
3638

37-
@Nullable
38-
static String extractInternalAppSharingArtifactId(@NonNull Context appContext) {
39+
/**
40+
* Get the package info for the currently installed app
41+
*
42+
* @throws FirebaseAppDistributionException if the package name can't be found
43+
*/
44+
static PackageInfo getPackageInfo(Context context) throws FirebaseAppDistributionException {
45+
return getPackageInfoWithFlags(context, NO_FLAGS);
46+
}
47+
48+
/**
49+
* Get the package info for the currently installed app, with the PackageManager.GET_META_DATA
50+
* flag set.
51+
*
52+
* @throws FirebaseAppDistributionException if the package name can't be found
53+
*/
54+
static PackageInfo getPackageInfoWithMetadata(Context context)
55+
throws FirebaseAppDistributionException {
56+
return getPackageInfoWithFlags(context, PackageManager.GET_META_DATA);
57+
}
58+
59+
private static PackageInfo getPackageInfoWithFlags(Context context, int flags)
60+
throws FirebaseAppDistributionException {
3961
try {
40-
PackageInfo packageInfo =
41-
appContext
42-
.getPackageManager()
43-
.getPackageInfo(appContext.getPackageName(), PackageManager.GET_META_DATA);
44-
if (packageInfo.applicationInfo.metaData == null) {
45-
return null;
46-
}
47-
return packageInfo.applicationInfo.metaData.getString("com.android.vending.internal.apk.id");
62+
return context.getPackageManager().getPackageInfo(context.getPackageName(), flags);
4863
} catch (PackageManager.NameNotFoundException e) {
49-
LogWrapper.getInstance().w(TAG + "Could not extract internal app sharing artifact ID");
50-
return null;
64+
throw new FirebaseAppDistributionException(
65+
"Unable to find package with name " + context.getPackageName(), Status.UNKNOWN, e);
66+
}
67+
}
68+
69+
static String extractInternalAppSharingArtifactId(@NonNull Context appContext)
70+
throws FirebaseAppDistributionException {
71+
PackageInfo packageInfo = getPackageInfoWithMetadata(appContext);
72+
if (packageInfo.applicationInfo.metaData == null) {
73+
throw new FirebaseAppDistributionException("Missing package info metadata", Status.UNKNOWN);
74+
}
75+
String id =
76+
packageInfo.applicationInfo.metaData.getString("com.android.vending.internal.apk.id");
77+
if (id == null) {
78+
throw new FirebaseAppDistributionException(
79+
"IAS artifact ID missing from package info metadata ", Status.UNKNOWN);
5180
}
81+
return id;
5282
}
5383

5484
@Nullable

firebase-app-distribution/src/main/java/com/google/firebase/app/distribution/TaskUtils.java

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,29 @@
1616

1717
import com.google.android.gms.tasks.Task;
1818
import com.google.android.gms.tasks.TaskCompletionSource;
19-
import com.google.android.gms.tasks.Tasks;
20-
import com.google.firebase.app.distribution.internal.LogWrapper;
19+
import com.google.firebase.app.distribution.FirebaseAppDistributionException.Status;
20+
import java.util.concurrent.Executor;
2121

2222
class TaskUtils {
23-
private static final String TAG = "TaskUtils:";
23+
interface Operation<TResult> {
24+
TResult run() throws FirebaseAppDistributionException;
25+
}
2426

25-
static <TResult> Task<TResult> handleTaskFailure(
26-
Task<TResult> task,
27-
String defaultErrorMessage,
28-
FirebaseAppDistributionException.Status defaultErrorStatus) {
29-
if (task.isComplete() && !task.isSuccessful()) {
30-
Exception e = task.getException();
31-
LogWrapper.getInstance().e(TAG + "Task failed to complete due to " + e.getMessage(), e);
32-
if (e instanceof FirebaseAppDistributionException) {
33-
return task;
34-
}
35-
return Tasks.forException(
36-
new FirebaseAppDistributionException(defaultErrorMessage, defaultErrorStatus, e));
37-
}
38-
return task;
27+
static <TResult> Task<TResult> runAsyncInTask(Executor executor, Operation<TResult> operation) {
28+
TaskCompletionSource<TResult> taskCompletionSource = new TaskCompletionSource<>();
29+
executor.execute(
30+
() -> {
31+
try {
32+
taskCompletionSource.setResult(operation.run());
33+
} catch (FirebaseAppDistributionException e) {
34+
taskCompletionSource.setException(e);
35+
} catch (Throwable t) {
36+
taskCompletionSource.setException(
37+
new FirebaseAppDistributionException(
38+
"Unknown error: " + t.getMessage(), Status.UNKNOWN, t));
39+
}
40+
});
41+
return taskCompletionSource.getTask();
3942
}
4043

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

0 commit comments

Comments
 (0)