Skip to content

Add logic to use the apk hash #2994

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

package com.google.firebase.appdistribution;

import static com.google.firebase.appdistribution.internal.ReleaseIdentificationUtils.calculateApkInternalCodeHash;
import static com.google.firebase.appdistribution.internal.ReleaseIdentificationUtils.calculateApkHash;

import android.content.Context;
import android.content.pm.PackageInfo;
Expand Down Expand Up @@ -43,7 +43,7 @@ class CheckForNewReleaseClient {
private final FirebaseApp firebaseApp;
private final FirebaseAppDistributionTesterApiClient firebaseAppDistributionTesterApiClient;
private final FirebaseInstallationsApi firebaseInstallationsApi;
private static final ConcurrentMap<String, String> cachedCodeHashes = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, String> cachedApkHashes = new ConcurrentHashMap<>();
private final ReleaseIdentifierStorage releaseIdentifierStorage;

Task<AppDistributionReleaseInternal> cachedCheckForNewRelease = null;
Expand All @@ -66,14 +66,13 @@ class CheckForNewReleaseClient {
@NonNull FirebaseApp firebaseApp,
@NonNull FirebaseAppDistributionTesterApiClient firebaseAppDistributionTesterApiClient,
@NonNull FirebaseInstallationsApi firebaseInstallationsApi,
@NonNull ReleaseIdentifierStorage releaseIdentifierStorage,
@NonNull Executor executor) {
this.firebaseApp = firebaseApp;
this.firebaseAppDistributionTesterApiClient = firebaseAppDistributionTesterApiClient;
this.firebaseInstallationsApi = firebaseInstallationsApi;
// TODO: verify if this is best way to use executorservice here
this.releaseIdentifierStorage = releaseIdentifierStorage;
this.checkForNewReleaseExecutor = executor;
this.releaseIdentifierStorage =
new ReleaseIdentifierStorage(firebaseApp.getApplicationContext());
}

@NonNull
Expand Down Expand Up @@ -128,7 +127,8 @@ AppDistributionReleaseInternal getNewReleaseFromClient(
firebaseAppDistributionTesterApiClient.fetchNewRelease(
fid, appId, apiKey, authToken, firebaseApp.getApplicationContext());

if (isNewerBuildVersion(retrievedNewRelease) || !isInstalledRelease(retrievedNewRelease)) {
if (isNewerBuildVersion(retrievedNewRelease)
|| !isSameAsInstalledRelease(retrievedNewRelease)) {
return retrievedNewRelease;
} else {
// Return null if retrieved new release is older or currently installed
Expand All @@ -150,9 +150,9 @@ private boolean isNewerBuildVersion(AppDistributionReleaseInternal newRelease)
}

@VisibleForTesting
boolean isInstalledRelease(AppDistributionReleaseInternal newRelease) {
boolean isSameAsInstalledRelease(AppDistributionReleaseInternal newRelease) {
if (newRelease.getBinaryType().equals(BinaryType.APK)) {
return hasSameCodeHashAsInstallledRelease(newRelease);
return hasSameHashAsInstalledRelease(newRelease);
}

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

@VisibleForTesting
String extractApkCodeHash(PackageInfo packageInfo) {
String extractApkHash(PackageInfo packageInfo) {
File sourceFile = new File(packageInfo.applicationInfo.sourceDir);

String key =
String.format(
Locale.ENGLISH, "%s.%d", sourceFile.getAbsolutePath(), sourceFile.lastModified());
if (!cachedCodeHashes.containsKey(key)) {
cachedCodeHashes.put(key, calculateApkInternalCodeHash(sourceFile));
if (!cachedApkHashes.containsKey(key)) {
cachedApkHashes.put(key, calculateApkHash(sourceFile));
}
return releaseIdentifierStorage.getExternalCodeHash(cachedCodeHashes.get(key));
return cachedApkHashes.get(key);
}

private boolean hasSameCodeHashAsInstallledRelease(AppDistributionReleaseInternal newRelease) {
private boolean hasSameHashAsInstalledRelease(AppDistributionReleaseInternal newRelease) {
try {
Context context = firebaseApp.getApplicationContext();
PackageInfo metadataPackageInfo =
context
.getPackageManager()
.getPackageInfo(context.getPackageName(), PackageManager.GET_META_DATA);
String externalCodeHash = extractApkCodeHash(metadataPackageInfo);
// Will trigger during the first install of the app since no zipHash to externalCodeHash
// mapping will have been set in ReleaseIdentifierStorage yet
if (externalCodeHash == null) {
return false;
String installedReleaseApkHash = extractApkHash(metadataPackageInfo);

if (installedReleaseApkHash.isEmpty() || newRelease.getApkHash().isEmpty()) {
// We don't have enough information about the APK hashes. Fallback to the external codehash.
// TODO: Consider removing this when all returned releases have the efficient ApkHash
String externalCodeHash =
releaseIdentifierStorage.getExternalCodeHash(installedReleaseApkHash);
return externalCodeHash != null && externalCodeHash.equals(newRelease.getCodeHash());
}

// If the codeHash for the retrieved newRelease is equal to the stored codeHash
// If the hash of the zipped APK for the retrieved newRelease is equal to the stored hash
// of the installed release, then they are the same release.
return externalCodeHash.equals(newRelease.getCodeHash());
return installedReleaseApkHash.equals(newRelease.getApkHash());
} catch (PackageManager.NameNotFoundException e) {
LogWrapper.getInstance().e(TAG + "Unable to locate App.", e);
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class FirebaseAppDistributionTesterApiClient {
private static final String RELEASE_NOTES_JSON_KEY = "releaseNotes";
private static final String BINARY_TYPE_JSON_KEY = "binaryType";
private static final String CODE_HASH_KEY = "codeHash";
private static final String APK_HASH_KEY = "apkHash";
private static final String IAS_ARTIFACT_ID_KEY = "iasArtifactId";
private static final String DOWNLOAD_URL_KEY = "downloadUrl";

Expand Down Expand Up @@ -81,6 +82,7 @@ class FirebaseAppDistributionTesterApiClient {
final String buildVersion = newReleaseJson.getString(BUILD_VERSION_JSON_KEY);
String releaseNotes = tryGetValue(newReleaseJson, RELEASE_NOTES_JSON_KEY);
String codeHash = tryGetValue(newReleaseJson, CODE_HASH_KEY);
String apkHash = tryGetValue(newReleaseJson, APK_HASH_KEY);
String iasArtifactId = tryGetValue(newReleaseJson, IAS_ARTIFACT_ID_KEY);
String downloadUrl = tryGetValue(newReleaseJson, DOWNLOAD_URL_KEY);

Expand All @@ -97,6 +99,7 @@ class FirebaseAppDistributionTesterApiClient {
.setBinaryType(binaryType)
.setIasArtifactId(iasArtifactId)
.setCodeHash(codeHash)
.setApkHash(apkHash)
.setDownloadUrl(downloadUrl)
.build();
inputStream.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import static com.google.firebase.appdistribution.FirebaseAppDistributionException.Status;
import static com.google.firebase.appdistribution.FirebaseAppDistributionException.Status.NETWORK_FAILURE;
import static com.google.firebase.appdistribution.internal.ReleaseIdentificationUtils.calculateApkInternalCodeHash;
import static com.google.firebase.appdistribution.internal.ReleaseIdentificationUtils.calculateApkHash;

import android.app.Activity;
import android.content.Context;
Expand Down Expand Up @@ -237,7 +237,7 @@ private void downloadToDisk(

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

String internalCodeHash = calculateApkInternalCodeHash(downloadedFile);
String internalCodeHash = calculateApkHash(downloadedFile);

if (internalCodeHash != null) {
releaseIdentifierStorage.setCodeHashMap(internalCodeHash, newRelease);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ public static Builder builder() {
@Nullable
public abstract String getCodeHash();

/** Efficient hash of an Android apk. Used to identify a release */
@Nullable
public abstract String getApkHash();

/**
* IAS artifact id. This value is inserted into the manifest of APK's installed via Used to map a
* release to an APK installed via an app bundle
Expand Down Expand Up @@ -85,6 +89,9 @@ public abstract static class Builder {
@NonNull
public abstract Builder setCodeHash(@NonNull String value);

@NonNull
public abstract Builder setApkHash(@NonNull String value);

@NonNull
public abstract Builder setIasArtifactId(@NonNull String value);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public static String extractInternalAppSharingArtifactId(@NonNull Context appCon
}

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

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

// Since calculating the codeHash returned from the release backend is computationally
// expensive, using existing checksum data from the ZipFile we can quickly calculate
// an intermediate hash that then gets mapped to the backend's returned release codehash
// expensive, we has the existing checksum data from the ZipFile and compare it to
// (1) the apk hash returned by the backend, or (2) look up a mapping from the apk zip hash to
// the
// full codehash, and compare that to the codehash to the backend
ZipFile zis = new ZipFile(file);
try {
Enumeration<? extends ZipEntry> zipEntries = zis.entries();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"downloadUrl":"http://test-url-apk",
"latest": true,
"codeHash": "code-hash-apk-1",
"apkHash": "apk-hash-1",
"fileSize": "3725041",
"expirationTime": "2021-12-04T17:10:03Z",
"binaryType": "APK"
Expand Down
Loading