Skip to content

Commit 32a9fd9

Browse files
author
Rachel Prince
committed
Add logic for the apk hash, but continue to fall back to the external code hash
1 parent 9b67604 commit 32a9fd9

File tree

7 files changed

+86
-64
lines changed

7 files changed

+86
-64
lines changed

-P

147 KB
Binary file not shown.

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

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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
@@ -152,7 +151,7 @@ private boolean isNewerBuildVersion(AppDistributionReleaseInternal newRelease)
152151
@VisibleForTesting
153152
boolean isInstalledRelease(AppDistributionReleaseInternal newRelease) {
154153
if (newRelease.getBinaryType().equals(BinaryType.APK)) {
155-
return hasSameCodeHashAsInstallledRelease(newRelease);
154+
return hasSameApkHashAsInstalledRelease(newRelease);
156155
}
157156

158157
if (newRelease.getIasArtifactId() == null) {
@@ -181,7 +180,7 @@ private long getInstalledAppVersionCode(Context context) throws FirebaseAppDistr
181180
}
182181

183182
@VisibleForTesting
184-
String extractApkCodeHash(PackageInfo packageInfo) {
183+
String extractApkHash(PackageInfo packageInfo) {
185184
File sourceFile = new File(packageInfo.applicationInfo.sourceDir);
186185

187186
String key =
@@ -190,26 +189,28 @@ String extractApkCodeHash(PackageInfo packageInfo) {
190189
if (!cachedCodeHashes.containsKey(key)) {
191190
cachedCodeHashes.put(key, calculateApkInternalCodeHash(sourceFile));
192191
}
193-
return releaseIdentifierStorage.getExternalCodeHash(cachedCodeHashes.get(key));
192+
return cachedCodeHashes.get(key);
194193
}
195194

196-
private boolean hasSameCodeHashAsInstallledRelease(AppDistributionReleaseInternal newRelease) {
195+
private boolean hasSameApkHashAsInstalledRelease(AppDistributionReleaseInternal newRelease) {
197196
try {
198197
Context context = firebaseApp.getApplicationContext();
199198
PackageInfo metadataPackageInfo =
200199
context
201200
.getPackageManager()
202201
.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;
202+
String installedReleaseApkHash = extractApkHash(metadataPackageInfo);
203+
204+
if (installedReleaseApkHash.isEmpty() || newRelease.getApkHash().isEmpty()) {
205+
// We don't have enough information about the APK hashes. Fallback to the external codehash.
206+
// TODO: Consider removing this when all returned releases have the efficient ApkHash
207+
String externalCodeHash =
208+
releaseIdentifierStorage.getExternalCodeHash(installedReleaseApkHash);
209+
return externalCodeHash != null && externalCodeHash.equals(newRelease.getCodeHash());
208210
}
209-
210-
// If the codeHash for the retrieved newRelease is equal to the stored codeHash
211+
// If the hash of the zipped APK for the retrieved newRelease is equal to the stored hash
211212
// of the installed release, then they are the same release.
212-
return externalCodeHash.equals(newRelease.getCodeHash());
213+
return installedReleaseApkHash.equals(newRelease.getApkHash());
213214
} catch (PackageManager.NameNotFoundException e) {
214215
LogWrapper.getInstance().e(TAG + "Unable to locate App.", e);
215216
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/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/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"

firebase-app-distribution/src/test/java/com/google/firebase/appdistribution/CheckForNewReleaseClientTest.java

Lines changed: 58 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
package com.google.firebase.appdistribution;
1616

17+
import static com.google.firebase.appdistribution.BinaryType.APK;
1718
import static org.junit.Assert.assertEquals;
1819
import static org.junit.Assert.assertFalse;
1920
import static org.junit.Assert.assertNotNull;
@@ -64,27 +65,12 @@ public class CheckForNewReleaseClientTest {
6465
private static final String TEST_AUTH_TOKEN = "fad.auth.token";
6566
private static final String TEST_IAS_ARTIFACT_ID = "ias-artifact-id";
6667
private static final String IAS_ARTIFACT_ID_KEY = "com.android.vending.internal.apk.id";
67-
private static final String TEST_CODEHASH_1 = "abcdef";
68-
private static final String TEST_CODEHASH_2 = "ghiklm";
69-
private static final long INSTALLED_VERSION_CODE = 2;
70-
71-
private static final AppDistributionReleaseInternal TEST_RELEASE_NEWER_APK =
72-
AppDistributionReleaseInternal.builder()
73-
.setBuildVersion("3")
74-
.setDisplayVersion("3.0")
75-
.setReleaseNotes("Newer version.")
76-
.setBinaryType(BinaryType.APK)
77-
.setCodeHash(TEST_CODEHASH_1)
78-
.build();
79-
80-
private static final AppDistributionReleaseInternal TEST_RELEASE_CURRENT =
81-
AppDistributionReleaseInternal.builder()
82-
.setBinaryType(BinaryType.APK)
83-
.setBuildVersion(Long.toString(INSTALLED_VERSION_CODE))
84-
.setDisplayVersion("2.0")
85-
.setReleaseNotes("Current version.")
86-
.setCodeHash(TEST_CODEHASH_2)
87-
.build();
68+
private static final String NEW_CODEHASH = "abcdef";
69+
private static final String CURRENT_CODEHASH = "ghiklm";
70+
private static final String NEW_APK_HASH = "newApkHash";
71+
private static final String CURRENT_APK_HASH = "currentApkHash";
72+
private static final long INSTALLED_VERSION_CODE = 1;
73+
private static final long NEW_VERSION_CODE = 2;
8874

8975
private CheckForNewReleaseClient checkForNewReleaseClient;
9076
private ShadowPackageManager shadowPackageManager;
@@ -93,6 +79,7 @@ public class CheckForNewReleaseClientTest {
9379
@Mock private FirebaseInstallationsApi mockFirebaseInstallations;
9480
@Mock private FirebaseAppDistributionTesterApiClient mockFirebaseAppDistributionTesterApiClient;
9581
@Mock private InstallationTokenResult mockInstallationTokenResult;
82+
@Mock private ReleaseIdentifierStorage mockReleaseIdentifierStorage;
9683

9784
Executor testExecutor = Executors.newSingleThreadExecutor();
9885

@@ -142,6 +129,7 @@ public void setup() {
142129
firebaseApp,
143130
mockFirebaseAppDistributionTesterApiClient,
144131
mockFirebaseInstallations,
132+
mockReleaseIdentifierStorage,
145133
testExecutor));
146134
}
147135

@@ -166,7 +154,7 @@ public void checkForNewReleaseTask_whenCalledMultipleTimes_returnsTheSameTask()
166154
public void checkForNewRelease_succeeds() throws Exception {
167155
when(mockFirebaseAppDistributionTesterApiClient.fetchNewRelease(
168156
any(), any(), any(), any(), any()))
169-
.thenReturn(TEST_RELEASE_NEWER_APK);
157+
.thenReturn(getTestNewRelease().build());
170158
when(mockFirebaseInstallations.getId()).thenReturn(Tasks.forResult(TEST_FID_1));
171159
when(mockFirebaseInstallations.getToken(false))
172160
.thenReturn(Tasks.forResult(mockInstallationTokenResult));
@@ -177,7 +165,7 @@ public void checkForNewRelease_succeeds() throws Exception {
177165
task.addOnCompleteListener(testExecutor, onCompleteListener);
178166

179167
AppDistributionReleaseInternal appDistributionReleaseInternal = onCompleteListener.await();
180-
assertEquals(TEST_RELEASE_NEWER_APK, appDistributionReleaseInternal);
168+
assertEquals(getTestNewRelease().build(), appDistributionReleaseInternal);
181169
verify(mockFirebaseInstallations, times(1)).getId();
182170
verify(mockFirebaseInstallations, times(1)).getToken(false);
183171
}
@@ -186,7 +174,7 @@ public void checkForNewRelease_succeeds() throws Exception {
186174
public void checkForNewRelease_nonAppDistroFailure() throws Exception {
187175
when(mockFirebaseAppDistributionTesterApiClient.fetchNewRelease(
188176
any(), any(), any(), any(), any()))
189-
.thenReturn(TEST_RELEASE_CURRENT);
177+
.thenReturn(getTestInstalledRelease().build());
190178
Exception expectedException = new Exception("test ex");
191179
when(mockFirebaseInstallations.getId()).thenReturn(Tasks.forException(expectedException));
192180
when(mockFirebaseInstallations.getToken(false))
@@ -235,23 +223,23 @@ public void getNewReleaseFromClient_whenNewReleaseIsNewerBuildThanInstalled_retu
235223
throws Exception {
236224
when(mockFirebaseAppDistributionTesterApiClient.fetchNewRelease(
237225
TEST_FID_1, TEST_APP_ID_1, TEST_API_KEY, TEST_AUTH_TOKEN, applicationContext))
238-
.thenReturn(TEST_RELEASE_NEWER_APK);
226+
.thenReturn(getTestNewRelease().build());
239227

240228
AppDistributionReleaseInternal release =
241229
checkForNewReleaseClient.getNewReleaseFromClient(
242230
TEST_FID_1, TEST_APP_ID_1, TEST_API_KEY, TEST_AUTH_TOKEN);
243231

244232
assertNotNull(release);
245-
assertEquals(TEST_RELEASE_NEWER_APK.getBuildVersion(), release.getBuildVersion());
233+
assertEquals(Long.toString(NEW_VERSION_CODE), release.getBuildVersion());
246234
}
247235

248236
@Test
249237
public void getNewReleaseFromClient_whenNewReleaseIsSameRelease_returnsNull() throws Exception {
250238
when(mockFirebaseAppDistributionTesterApiClient.fetchNewRelease(
251239
TEST_FID_1, TEST_APP_ID_1, TEST_API_KEY, TEST_AUTH_TOKEN, applicationContext))
252-
.thenReturn(TEST_RELEASE_CURRENT);
240+
.thenReturn(getTestInstalledRelease().build());
253241

254-
doReturn(TEST_CODEHASH_2).when(checkForNewReleaseClient).extractApkCodeHash(any());
242+
doReturn(CURRENT_APK_HASH).when(checkForNewReleaseClient).extractApkHash(any());
255243

256244
AppDistributionReleaseInternal release =
257245
checkForNewReleaseClient.getNewReleaseFromClient(
@@ -265,10 +253,7 @@ public void handleNewReleaseFromClient_whenNewAabIsAvailable_returnsRelease() th
265253
when(mockFirebaseAppDistributionTesterApiClient.fetchNewRelease(
266254
any(), any(), any(), any(), any()))
267255
.thenReturn(
268-
AppDistributionReleaseInternal.builder()
269-
.setBuildVersion(TEST_RELEASE_CURRENT.getBuildVersion())
270-
.setDisplayVersion(TEST_RELEASE_CURRENT.getDisplayVersion())
271-
.setCodeHash("codehash")
256+
getTestNewRelease()
272257
.setDownloadUrl("http://fake-download-url")
273258
.setIasArtifactId("test-ias-artifact-id-2")
274259
.setBinaryType(BinaryType.AAB)
@@ -278,10 +263,7 @@ public void handleNewReleaseFromClient_whenNewAabIsAvailable_returnsRelease() th
278263
checkForNewReleaseClient.getNewReleaseFromClient(
279264
TEST_FID_1, TEST_APP_ID_1, TEST_API_KEY, TEST_AUTH_TOKEN);
280265
assertEquals(
281-
AppDistributionReleaseInternal.builder()
282-
.setBuildVersion(TEST_RELEASE_CURRENT.getBuildVersion())
283-
.setDisplayVersion(TEST_RELEASE_CURRENT.getDisplayVersion())
284-
.setCodeHash("codehash")
266+
getTestNewRelease()
285267
.setDownloadUrl("http://fake-download-url")
286268
.setIasArtifactId("test-ias-artifact-id-2")
287269
.setBinaryType(BinaryType.AAB)
@@ -295,10 +277,7 @@ public void handleNewReleaseFromClient_whenNewReleaseIsSameAsInstalledAab_return
295277
when(mockFirebaseAppDistributionTesterApiClient.fetchNewRelease(
296278
any(), any(), any(), any(), any()))
297279
.thenReturn(
298-
AppDistributionReleaseInternal.builder()
299-
.setBuildVersion(TEST_RELEASE_CURRENT.getBuildVersion())
300-
.setDisplayVersion(TEST_RELEASE_CURRENT.getDisplayVersion())
301-
.setCodeHash("codehash")
280+
getTestInstalledRelease()
302281
.setDownloadUrl("http://fake-download-url")
303282
.setIasArtifactId(TEST_IAS_ARTIFACT_ID)
304283
.setBinaryType(BinaryType.AAB)
@@ -311,15 +290,25 @@ public void handleNewReleaseFromClient_whenNewReleaseIsSameAsInstalledAab_return
311290
}
312291

313292
@Test
314-
public void isInstalledRelease_whenCodeHashesEqual_returnsTrue() {
315-
doReturn(TEST_CODEHASH_1).when(checkForNewReleaseClient).extractApkCodeHash(any());
316-
assertTrue(checkForNewReleaseClient.isInstalledRelease(TEST_RELEASE_NEWER_APK));
293+
public void isInstalledRelease_whenApkHashesEqual_returnsTrue() {
294+
doReturn(CURRENT_APK_HASH).when(checkForNewReleaseClient).extractApkHash(any());
295+
assertTrue(checkForNewReleaseClient.isInstalledRelease(getTestInstalledRelease().build()));
317296
}
318297

319298
@Test
320-
public void isInstalledRelease_whenCodeHashesNotEqual_returnsFalse() {
321-
doReturn(TEST_CODEHASH_2).when(checkForNewReleaseClient).extractApkCodeHash(any());
322-
assertFalse(checkForNewReleaseClient.isInstalledRelease(TEST_RELEASE_NEWER_APK));
299+
public void isInstalledRelease_whenApkHashesNotEqual_returnsFalse() {
300+
doReturn(CURRENT_APK_HASH).when(checkForNewReleaseClient).extractApkHash(any());
301+
assertFalse(checkForNewReleaseClient.isInstalledRelease(getTestNewRelease().build()));
302+
}
303+
304+
@Test
305+
public void isInstalledRelease_ifApkHashNotPresent_fallsBackToExternalCodeHash() {
306+
doReturn(CURRENT_APK_HASH).when(checkForNewReleaseClient).extractApkHash(any());
307+
when(mockReleaseIdentifierStorage.getExternalCodeHash(any())).thenReturn(CURRENT_CODEHASH);
308+
309+
assertFalse(
310+
checkForNewReleaseClient.isInstalledRelease(getTestNewRelease().setApkHash("").build()));
311+
verify(mockReleaseIdentifierStorage).getExternalCodeHash(CURRENT_APK_HASH);
323312
}
324313

325314
@Test
@@ -332,13 +321,33 @@ public void extractApkCodeHash_ifKeyInCachedCodeHashes_doesNotRecalculateZipHash
332321
ApplicationProvider.getApplicationContext().getPackageName());
333322
mockedReleaseIdentificationUtils
334323
.when(() -> ReleaseIdentificationUtils.calculateApkInternalCodeHash(any()))
335-
.thenReturn(TEST_CODEHASH_1);
324+
.thenReturn(NEW_CODEHASH);
336325

337-
checkForNewReleaseClient.extractApkCodeHash(packageInfo);
338-
checkForNewReleaseClient.extractApkCodeHash(packageInfo);
326+
checkForNewReleaseClient.extractApkHash(packageInfo);
327+
checkForNewReleaseClient.extractApkHash(packageInfo);
339328
// check that calculateApkInternalCodeHash is only called once
340329
mockedReleaseIdentificationUtils.verify(
341330
() -> ReleaseIdentificationUtils.calculateApkInternalCodeHash(any()));
342331
}
343332
}
333+
334+
private AppDistributionReleaseInternal.Builder getTestNewRelease() {
335+
return AppDistributionReleaseInternal.builder()
336+
.setBuildVersion(Long.toString(NEW_VERSION_CODE))
337+
.setDisplayVersion("2.0")
338+
.setReleaseNotes("Newer version.")
339+
.setCodeHash(NEW_CODEHASH)
340+
.setBinaryType(APK)
341+
.setApkHash(NEW_APK_HASH);
342+
}
343+
344+
private AppDistributionReleaseInternal.Builder getTestInstalledRelease() {
345+
return AppDistributionReleaseInternal.builder()
346+
.setBuildVersion(Long.toString(INSTALLED_VERSION_CODE))
347+
.setDisplayVersion("1.0")
348+
.setReleaseNotes("Current version.")
349+
.setCodeHash(CURRENT_CODEHASH)
350+
.setBinaryType(APK)
351+
.setApkHash(CURRENT_APK_HASH);
352+
}
344353
}

firebase-app-distribution/src/test/java/com/google/firebase/appdistribution/FirebaseAppDistributionTesterApiClientTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public void fetchNewRelease_whenResponseSuccessfulForApk_returnsRelease() throws
8181
assertEquals(release.getReleaseNotes(), "This is a test release.");
8282
assertEquals(release.getDownloadUrl(), "http://test-url-apk");
8383
assertEquals(release.getCodeHash(), "code-hash-apk-1");
84+
assertEquals(release.getApkHash(), "apk-hash-1");
8485
}
8586

8687
@Test

0 commit comments

Comments
 (0)