Skip to content

Commit 8db1a2a

Browse files
committed
Clean up task management in NewReleaseFetcher
1 parent 444edc1 commit 8db1a2a

File tree

5 files changed

+160
-119
lines changed

5 files changed

+160
-119
lines changed

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

Lines changed: 84 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,21 @@
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.Constants.ErrorMessages;
32+
import com.google.firebase.app.distribution.FirebaseAppDistributionException.Status;
2833
import com.google.firebase.app.distribution.internal.LogWrapper;
2934
import com.google.firebase.inject.Provider;
3035
import com.google.firebase.installations.FirebaseInstallationsApi;
@@ -87,82 +92,72 @@ public synchronized Task<AppDistributionReleaseInternal> checkForNewRelease() {
8792

8893
this.cachedCheckForNewRelease =
8994
Tasks.whenAllSuccess(installationIdTask, installationAuthTokenTask)
95+
.continueWithTask(TaskUtils::handleTaskFailure)
9096
.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));
97+
unused ->
98+
getNewReleaseFromClientTask(
99+
installationIdTask.getResult(),
100+
firebaseApp.getOptions().getApplicationId(),
101+
firebaseApp.getOptions().getApiKey(),
102+
installationAuthTokenTask.getResult().getToken()));
115103

116104
return cachedCheckForNewRelease;
117105
}
118106

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

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

162-
private boolean isNewerBuildVersion(AppDistributionReleaseInternal newRelease)
158+
private boolean isNewerBuildVersion(long newReleaseBuildVersion)
163159
throws FirebaseAppDistributionException {
164-
return Long.parseLong(newRelease.getBuildVersion())
165-
> getInstalledAppVersionCode(firebaseApp.getApplicationContext());
160+
return newReleaseBuildVersion > getInstalledAppVersionCode(firebaseApp.getApplicationContext());
166161
}
167162

168163
private boolean hasDifferentAppVersionName(AppDistributionReleaseInternal newRelease)
@@ -179,17 +174,26 @@ boolean isSameAsInstalledRelease(AppDistributionReleaseInternal newRelease)
179174
return hasSameHashAsInstalledRelease(newRelease);
180175
}
181176

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

195199
private long getInstalledAppVersionCode(Context context) throws FirebaseAppDistributionException {
@@ -201,19 +205,6 @@ private String getInstalledAppVersionName(Context context)
201205
return getPackageInfo(context).versionName;
202206
}
203207

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-
217208
@VisibleForTesting
218209
String extractApkHash(PackageInfo packageInfo) {
219210
File sourceFile = new File(packageInfo.applicationInfo.sourceDir);
@@ -229,25 +220,19 @@ String extractApkHash(PackageInfo packageInfo) {
229220

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

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: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,44 @@
1717
import com.google.android.gms.tasks.Task;
1818
import com.google.android.gms.tasks.TaskCompletionSource;
1919
import com.google.android.gms.tasks.Tasks;
20+
import com.google.firebase.app.distribution.Constants.ErrorMessages;
21+
import com.google.firebase.app.distribution.FirebaseAppDistributionException.Status;
2022
import com.google.firebase.app.distribution.internal.LogWrapper;
23+
import java.util.concurrent.Executor;
2124

2225
class TaskUtils {
2326
private static final String TAG = "TaskUtils:";
2427

25-
static <TResult> Task<TResult> handleTaskFailure(
26-
Task<TResult> task,
27-
String defaultErrorMessage,
28-
FirebaseAppDistributionException.Status defaultErrorStatus) {
28+
interface Operation<TResult> {
29+
TResult run() throws FirebaseAppDistributionException;
30+
}
31+
32+
static <TResult> Task<TResult> runAsyncInTask(Executor executor, Operation<TResult> operation) {
33+
TaskCompletionSource<TResult> taskCompletionSource = new TaskCompletionSource<>();
34+
executor.execute(
35+
() -> {
36+
try {
37+
taskCompletionSource.setResult(operation.run());
38+
} catch (FirebaseAppDistributionException e) {
39+
taskCompletionSource.setException(e);
40+
} catch (Throwable t) {
41+
taskCompletionSource.setException(
42+
new FirebaseAppDistributionException(
43+
"Unknown error: " + t.getMessage(), Status.UNKNOWN, t));
44+
}
45+
});
46+
return taskCompletionSource.getTask();
47+
}
48+
49+
static <TResult> Task<TResult> handleTaskFailure(Task<TResult> task) {
2950
if (task.isComplete() && !task.isSuccessful()) {
3051
Exception e = task.getException();
3152
LogWrapper.getInstance().e(TAG + "Task failed to complete due to " + e.getMessage(), e);
3253
if (e instanceof FirebaseAppDistributionException) {
3354
return task;
3455
}
3556
return Tasks.forException(
36-
new FirebaseAppDistributionException(defaultErrorMessage, defaultErrorStatus, e));
57+
new FirebaseAppDistributionException(ErrorMessages.UNKNOWN_ERROR, Status.UNKNOWN, e));
3758
}
3859
return task;
3960
}

firebase-app-distribution/src/main/java/com/google/firebase/app/distribution/internal/LogWrapper.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ public void w(@NonNull String msg) {
4848
Log.w(LOG_TAG, msg);
4949
}
5050

51+
public void w(@NonNull String msg, @NonNull Throwable tr) {
52+
Log.w(LOG_TAG, msg, tr);
53+
}
54+
5155
public void e(@NonNull String msg) {
5256
Log.e(LOG_TAG, msg);
5357
}

0 commit comments

Comments
 (0)