Skip to content

Commit b0a78ba

Browse files
author
rachaprince
authored
Add logic to use the apk hash (#2994)
* Add logic for the apk hash, but continue to fall back to the external code hash * Rename functions for clarity * delete binary file
1 parent 9815e45 commit b0a78ba

File tree

8 files changed

+105
-78
lines changed

8 files changed

+105
-78
lines changed

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

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
package com.google.firebase.appdistribution;
1616

17-
import static com.google.firebase.appdistribution.internal.ReleaseIdentificationUtils.calculateApkInternalCodeHash;
17+
import static com.google.firebase.appdistribution.internal.ReleaseIdentificationUtils.calculateApkHash;
1818

1919
import android.content.Context;
2020
import android.content.pm.PackageInfo;
@@ -43,7 +43,7 @@ class CheckForNewReleaseClient {
4343
private final FirebaseApp firebaseApp;
4444
private final FirebaseAppDistributionTesterApiClient firebaseAppDistributionTesterApiClient;
4545
private final FirebaseInstallationsApi firebaseInstallationsApi;
46-
private static final ConcurrentMap<String, String> cachedCodeHashes = new ConcurrentHashMap<>();
46+
private static final ConcurrentMap<String, String> cachedApkHashes = new ConcurrentHashMap<>();
4747
private final ReleaseIdentifierStorage releaseIdentifierStorage;
4848

4949
Task<AppDistributionReleaseInternal> cachedCheckForNewRelease = null;
@@ -66,14 +66,13 @@ class CheckForNewReleaseClient {
6666
@NonNull FirebaseApp firebaseApp,
6767
@NonNull FirebaseAppDistributionTesterApiClient firebaseAppDistributionTesterApiClient,
6868
@NonNull FirebaseInstallationsApi firebaseInstallationsApi,
69+
@NonNull ReleaseIdentifierStorage releaseIdentifierStorage,
6970
@NonNull Executor executor) {
7071
this.firebaseApp = firebaseApp;
7172
this.firebaseAppDistributionTesterApiClient = firebaseAppDistributionTesterApiClient;
7273
this.firebaseInstallationsApi = firebaseInstallationsApi;
73-
// TODO: verify if this is best way to use executorservice here
74+
this.releaseIdentifierStorage = releaseIdentifierStorage;
7475
this.checkForNewReleaseExecutor = executor;
75-
this.releaseIdentifierStorage =
76-
new ReleaseIdentifierStorage(firebaseApp.getApplicationContext());
7776
}
7877

7978
@NonNull
@@ -128,7 +127,8 @@ AppDistributionReleaseInternal getNewReleaseFromClient(
128127
firebaseAppDistributionTesterApiClient.fetchNewRelease(
129128
fid, appId, apiKey, authToken, firebaseApp.getApplicationContext());
130129

131-
if (isNewerBuildVersion(retrievedNewRelease) || !isInstalledRelease(retrievedNewRelease)) {
130+
if (isNewerBuildVersion(retrievedNewRelease)
131+
|| !isSameAsInstalledRelease(retrievedNewRelease)) {
132132
return retrievedNewRelease;
133133
} else {
134134
// Return null if retrieved new release is older or currently installed
@@ -150,9 +150,9 @@ private boolean isNewerBuildVersion(AppDistributionReleaseInternal newRelease)
150150
}
151151

152152
@VisibleForTesting
153-
boolean isInstalledRelease(AppDistributionReleaseInternal newRelease) {
153+
boolean isSameAsInstalledRelease(AppDistributionReleaseInternal newRelease) {
154154
if (newRelease.getBinaryType().equals(BinaryType.APK)) {
155-
return hasSameCodeHashAsInstallledRelease(newRelease);
155+
return hasSameHashAsInstalledRelease(newRelease);
156156
}
157157

158158
if (newRelease.getIasArtifactId() == null) {
@@ -181,35 +181,37 @@ private long getInstalledAppVersionCode(Context context) throws FirebaseAppDistr
181181
}
182182

183183
@VisibleForTesting
184-
String extractApkCodeHash(PackageInfo packageInfo) {
184+
String extractApkHash(PackageInfo packageInfo) {
185185
File sourceFile = new File(packageInfo.applicationInfo.sourceDir);
186186

187187
String key =
188188
String.format(
189189
Locale.ENGLISH, "%s.%d", sourceFile.getAbsolutePath(), sourceFile.lastModified());
190-
if (!cachedCodeHashes.containsKey(key)) {
191-
cachedCodeHashes.put(key, calculateApkInternalCodeHash(sourceFile));
190+
if (!cachedApkHashes.containsKey(key)) {
191+
cachedApkHashes.put(key, calculateApkHash(sourceFile));
192192
}
193-
return releaseIdentifierStorage.getExternalCodeHash(cachedCodeHashes.get(key));
193+
return cachedApkHashes.get(key);
194194
}
195195

196-
private boolean hasSameCodeHashAsInstallledRelease(AppDistributionReleaseInternal newRelease) {
196+
private boolean hasSameHashAsInstalledRelease(AppDistributionReleaseInternal newRelease) {
197197
try {
198198
Context context = firebaseApp.getApplicationContext();
199199
PackageInfo metadataPackageInfo =
200200
context
201201
.getPackageManager()
202202
.getPackageInfo(context.getPackageName(), PackageManager.GET_META_DATA);
203-
String externalCodeHash = extractApkCodeHash(metadataPackageInfo);
204-
// Will trigger during the first install of the app since no zipHash to externalCodeHash
205-
// mapping will have been set in ReleaseIdentifierStorage yet
206-
if (externalCodeHash == null) {
207-
return false;
203+
String installedReleaseApkHash = extractApkHash(metadataPackageInfo);
204+
205+
if (installedReleaseApkHash.isEmpty() || newRelease.getApkHash().isEmpty()) {
206+
// We don't have enough information about the APK hashes. Fallback to the external codehash.
207+
// TODO: Consider removing this when all returned releases have the efficient ApkHash
208+
String externalCodeHash =
209+
releaseIdentifierStorage.getExternalCodeHash(installedReleaseApkHash);
210+
return externalCodeHash != null && externalCodeHash.equals(newRelease.getCodeHash());
208211
}
209-
210-
// If the codeHash for the retrieved newRelease is equal to the stored codeHash
212+
// If the hash of the zipped APK for the retrieved newRelease is equal to the stored hash
211213
// of the installed release, then they are the same release.
212-
return externalCodeHash.equals(newRelease.getCodeHash());
214+
return installedReleaseApkHash.equals(newRelease.getApkHash());
213215
} catch (PackageManager.NameNotFoundException e) {
214216
LogWrapper.getInstance().e(TAG + "Unable to locate App.", e);
215217
return false;

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class FirebaseAppDistributionTesterApiClient {
5050
private static final String RELEASE_NOTES_JSON_KEY = "releaseNotes";
5151
private static final String BINARY_TYPE_JSON_KEY = "binaryType";
5252
private static final String CODE_HASH_KEY = "codeHash";
53+
private static final String APK_HASH_KEY = "apkHash";
5354
private static final String IAS_ARTIFACT_ID_KEY = "iasArtifactId";
5455
private static final String DOWNLOAD_URL_KEY = "downloadUrl";
5556

@@ -81,6 +82,7 @@ class FirebaseAppDistributionTesterApiClient {
8182
final String buildVersion = newReleaseJson.getString(BUILD_VERSION_JSON_KEY);
8283
String releaseNotes = tryGetValue(newReleaseJson, RELEASE_NOTES_JSON_KEY);
8384
String codeHash = tryGetValue(newReleaseJson, CODE_HASH_KEY);
85+
String apkHash = tryGetValue(newReleaseJson, APK_HASH_KEY);
8486
String iasArtifactId = tryGetValue(newReleaseJson, IAS_ARTIFACT_ID_KEY);
8587
String downloadUrl = tryGetValue(newReleaseJson, DOWNLOAD_URL_KEY);
8688

@@ -97,6 +99,7 @@ class FirebaseAppDistributionTesterApiClient {
9799
.setBinaryType(binaryType)
98100
.setIasArtifactId(iasArtifactId)
99101
.setCodeHash(codeHash)
102+
.setApkHash(apkHash)
100103
.setDownloadUrl(downloadUrl)
101104
.build();
102105
inputStream.close();

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import static com.google.firebase.appdistribution.FirebaseAppDistributionException.Status;
1818
import static com.google.firebase.appdistribution.FirebaseAppDistributionException.Status.NETWORK_FAILURE;
19-
import static com.google.firebase.appdistribution.internal.ReleaseIdentificationUtils.calculateApkInternalCodeHash;
19+
import static com.google.firebase.appdistribution.internal.ReleaseIdentificationUtils.calculateApkHash;
2020

2121
import android.app.Activity;
2222
import android.content.Context;
@@ -237,7 +237,7 @@ private void downloadToDisk(
237237

238238
File downloadedFile = new File(firebaseApp.getApplicationContext().getFilesDir(), fileName);
239239

240-
String internalCodeHash = calculateApkInternalCodeHash(downloadedFile);
240+
String internalCodeHash = calculateApkHash(downloadedFile);
241241

242242
if (internalCodeHash != null) {
243243
releaseIdentifierStorage.setCodeHashMap(internalCodeHash, newRelease);

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ public static Builder builder() {
5555
@Nullable
5656
public abstract String getCodeHash();
5757

58+
/** Efficient hash of an Android apk. Used to identify a release */
59+
@Nullable
60+
public abstract String getApkHash();
61+
5862
/**
5963
* IAS artifact id. This value is inserted into the manifest of APK's installed via Used to map a
6064
* release to an APK installed via an app bundle
@@ -85,6 +89,9 @@ public abstract static class Builder {
8589
@NonNull
8690
public abstract Builder setCodeHash(@NonNull String value);
8791

92+
@NonNull
93+
public abstract Builder setApkHash(@NonNull String value);
94+
8895
@NonNull
8996
public abstract Builder setIasArtifactId(@NonNull String value);
9097

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public static String extractInternalAppSharingArtifactId(@NonNull Context appCon
5252
}
5353

5454
@Nullable
55-
public static String calculateApkInternalCodeHash(@NonNull File file) {
55+
public static String calculateApkHash(@NonNull File file) {
5656
Log.v(TAG, String.format("Calculating release id for %s", file.getPath()));
5757
Log.v(TAG, String.format("File size: %d", file.length()));
5858

@@ -64,8 +64,10 @@ public static String calculateApkInternalCodeHash(@NonNull File file) {
6464
ArrayList<Byte> checksums = new ArrayList<>();
6565

6666
// Since calculating the codeHash returned from the release backend is computationally
67-
// expensive, using existing checksum data from the ZipFile we can quickly calculate
68-
// an intermediate hash that then gets mapped to the backend's returned release codehash
67+
// expensive, we has the existing checksum data from the ZipFile and compare it to
68+
// (1) the apk hash returned by the backend, or (2) look up a mapping from the apk zip hash to
69+
// the
70+
// full codehash, and compare that to the codehash to the backend
6971
ZipFile zis = new ZipFile(file);
7072
try {
7173
Enumeration<? extends ZipEntry> zipEntries = zis.entries();

firebase-app-distribution/src/test/assets/testApkReleaseResponse.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"downloadUrl":"http://test-url-apk",
99
"latest": true,
1010
"codeHash": "code-hash-apk-1",
11+
"apkHash": "apk-hash-1",
1112
"fileSize": "3725041",
1213
"expirationTime": "2021-12-04T17:10:03Z",
1314
"binaryType": "APK"

0 commit comments

Comments
 (0)