Skip to content

Commit 6178b13

Browse files
authored
Clean up error handling in NewReleaseFetcher (#3279)
* Clean up task management in NewReleaseFetcher * Add javadocs
1 parent e1de9ed commit 6178b13

File tree

5 files changed

+219
-138
lines changed

5 files changed

+219
-138
lines changed

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

Lines changed: 83 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;
@@ -87,82 +91,72 @@ public synchronized Task<AppDistributionReleaseInternal> checkForNewRelease() {
8791

8892
this.cachedCheckForNewRelease =
8993
Tasks.whenAllSuccess(installationIdTask, installationAuthTokenTask)
94+
.continueWithTask(TaskUtils::handleTaskFailure)
9095
.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));
96+
unused ->
97+
getNewReleaseFromClientTask(
98+
installationIdTask.getResult(),
99+
firebaseApp.getOptions().getApplicationId(),
100+
firebaseApp.getOptions().getApiKey(),
101+
installationAuthTokenTask.getResult().getToken()));
115102

116103
return cachedCheckForNewRelease;
117104
}
118105

106+
private Task<AppDistributionReleaseInternal> getNewReleaseFromClientTask(
107+
String fid, String appId, String apiKey, String authToken) {
108+
return runAsyncInTask(
109+
taskExecutor, () -> getNewReleaseFromClient(fid, appId, apiKey, authToken));
110+
}
111+
112+
@Nullable
119113
@VisibleForTesting
120114
AppDistributionReleaseInternal getNewReleaseFromClient(
121115
String fid, String appId, String apiKey, String authToken)
122116
throws FirebaseAppDistributionException {
117+
AppDistributionReleaseInternal retrievedNewRelease =
118+
firebaseAppDistributionTesterApiClient.fetchNewRelease(
119+
fid, appId, apiKey, authToken, firebaseApp.getApplicationContext());
120+
121+
if (retrievedNewRelease == null) {
122+
LogWrapper.getInstance().v(TAG + "Tester does not have access to any releases");
123+
return null;
124+
}
125+
126+
long newReleaseBuildVersion = parseBuildVersion(retrievedNewRelease.getBuildVersion());
127+
128+
if (isOlderBuildVersion(newReleaseBuildVersion)) {
129+
LogWrapper.getInstance().v(TAG + "New release has lower version code than current release");
130+
return null;
131+
}
132+
133+
if (isNewerBuildVersion(newReleaseBuildVersion)
134+
|| !isSameAsInstalledRelease(retrievedNewRelease)
135+
|| hasDifferentAppVersionName(retrievedNewRelease)) {
136+
return retrievedNewRelease;
137+
} else {
138+
LogWrapper.getInstance().v(TAG + "New release is older or is currently installed");
139+
return null;
140+
}
141+
}
142+
143+
private long parseBuildVersion(String buildVersion) throws FirebaseAppDistributionException {
123144
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-
}
145+
return Long.parseLong(buildVersion);
147146
} catch (NumberFormatException e) {
148-
LogWrapper.getInstance().e(TAG + "Error parsing buildVersion.", e);
149147
throw new FirebaseAppDistributionException(
150-
Constants.ErrorMessages.NETWORK_ERROR,
151-
FirebaseAppDistributionException.Status.NETWORK_FAILURE,
152-
e);
148+
"Could not parse build version of new release: " + buildVersion, Status.UNKNOWN, e);
153149
}
154150
}
155151

156-
private boolean canInstall(AppDistributionReleaseInternal newRelease)
152+
private boolean isOlderBuildVersion(long newReleaseBuildVersion)
157153
throws FirebaseAppDistributionException {
158-
return Long.parseLong(newRelease.getBuildVersion())
159-
>= getInstalledAppVersionCode(firebaseApp.getApplicationContext());
154+
return newReleaseBuildVersion < getInstalledAppVersionCode(firebaseApp.getApplicationContext());
160155
}
161156

162-
private boolean isNewerBuildVersion(AppDistributionReleaseInternal newRelease)
157+
private boolean isNewerBuildVersion(long newReleaseBuildVersion)
163158
throws FirebaseAppDistributionException {
164-
return Long.parseLong(newRelease.getBuildVersion())
165-
> getInstalledAppVersionCode(firebaseApp.getApplicationContext());
159+
return newReleaseBuildVersion > getInstalledAppVersionCode(firebaseApp.getApplicationContext());
166160
}
167161

168162
private boolean hasDifferentAppVersionName(AppDistributionReleaseInternal newRelease)
@@ -179,17 +173,26 @@ boolean isSameAsInstalledRelease(AppDistributionReleaseInternal newRelease)
179173
return hasSameHashAsInstalledRelease(newRelease);
180174
}
181175

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) {
176+
if (newRelease.getIasArtifactId() == null || newRelease.getIasArtifactId().isEmpty()) {
177+
LogWrapper.getInstance()
178+
.w(TAG + "AAB release missing IAS Artifact ID. Assuming new release is different.");
185179
return false;
186180
}
187-
// AAB BinaryType
188-
return newRelease
189-
.getIasArtifactId()
190-
.equals(
191-
ReleaseIdentificationUtils.extractInternalAppSharingArtifactId(
192-
firebaseApp.getApplicationContext()));
181+
182+
String installedIasArtifactId;
183+
try {
184+
installedIasArtifactId =
185+
ReleaseIdentificationUtils.extractInternalAppSharingArtifactId(
186+
firebaseApp.getApplicationContext());
187+
} catch (FirebaseAppDistributionException e) {
188+
LogWrapper.getInstance()
189+
.w(
190+
TAG + "Could not get installed IAS artifact ID. Assuming new release is different.",
191+
e);
192+
return false;
193+
}
194+
195+
return newRelease.getIasArtifactId().equals(installedIasArtifactId);
193196
}
194197

195198
private long getInstalledAppVersionCode(Context context) throws FirebaseAppDistributionException {
@@ -201,19 +204,6 @@ private String getInstalledAppVersionName(Context context)
201204
return getPackageInfo(context).versionName;
202205
}
203206

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-
217207
@VisibleForTesting
218208
String extractApkHash(PackageInfo packageInfo) {
219209
File sourceFile = new File(packageInfo.applicationInfo.sourceDir);
@@ -229,25 +219,19 @@ String extractApkHash(PackageInfo packageInfo) {
229219

230220
private boolean hasSameHashAsInstalledRelease(AppDistributionReleaseInternal newRelease)
231221
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;
222+
Context context = firebaseApp.getApplicationContext();
223+
PackageInfo metadataPackageInfo = getPackageInfoWithMetadata(context);
224+
String installedReleaseApkHash = extractApkHash(metadataPackageInfo);
225+
226+
if (installedReleaseApkHash == null || installedReleaseApkHash.isEmpty()) {
227+
throw new FirebaseAppDistributionException(
228+
"Could not calculate hash of installed APK", Status.UNKNOWN);
229+
} else if (newRelease.getApkHash().isEmpty()) {
230+
throw new FirebaseAppDistributionException(
231+
"Missing APK hash from new release", Status.UNKNOWN);
251232
}
233+
// If the hash of the zipped APK for the retrieved newRelease is equal to the stored hash
234+
// of the installed release, then they are the same release.
235+
return installedReleaseApkHash.equals(newRelease.getApkHash());
252236
}
253237
}

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

0 commit comments

Comments
 (0)