soul-browser/sources/java/com/google/api/client/googleapis/media/MediaHttpDownloader.java
KaKi87 550ddb0413
Decompile and recreate Soul Browser v1.4.85 with APK CI builds (#1)
* Decompile and recreate Soul Browser v1.4.85 with APK CI builds

Co-authored-by: KaKi87 <KaKi87@pm.me>

* Fix CI build: include bundled google.jks and remove PR trigger

The apktool build failed in CI because app/unknown/.../google.jks was
excluded by the *.jks gitignore rule. Whitelist app-bundled JKS files
and commit the missing resource.

Also remove the redundant pull_request workflow trigger.

Co-authored-by: KaKi87 <KaKi87@pm.me>

* Change package ID to com.github.kaki87.soulbrowser

Rename the application ID so the rebuilt APK can be installed
alongside the original Soul Browser from Google Play.

- Update AndroidManifest package, permissions, providers, and actions
- Update R class references in smali (com/github/kaki87/soulbrowser/R)
- Update shortcuts.xml targetPackage
- Rename app label to "Soul Rebuild" for easy identification

Co-authored-by: KaKi87 <KaKi87@pm.me>

* Fix install compatibility: remove split APK metadata and bundle native libs

The rebuilt APK still declared requiredSplitTypes and Play Store split
metadata, causing Android to reject standalone installs as incompatible.

- Remove requiredSplitTypes and split-related manifest meta-data
- Merge native libs from all ABI splits with uncompressed storage
- Page-align before signing for extractNativeLibs=false
- Add .so to apktool doNotCompress list

Co-authored-by: KaKi87 <KaKi87@pm.me>

* Fix install failure: use apksigner v2/v3 and bundle arm64 native libs

Target SDK 36 requires APK Signature Scheme v2+, but jarsigner only
produces v1 signatures, causing Android to reject the install.

- Sign with apksigner (v1+v2+v3) instead of jarsigner
- Add arm64-v8a native libraries from universal Soul Browser 1.4.79
- Set extractNativeLibs=true for reliable sideload installs
- Commit stable debug keystore for consistent signatures across builds

Co-authored-by: KaKi87 <KaKi87@pm.me>

* Add committed debug keystore for consistent APK signatures

Co-authored-by: KaKi87 <KaKi87@pm.me>

* Fix startup crash: use original classes2.dex desugar libraries

Apktool recompiles smali_classes2 into a broken classes2.dex, causing
ClassNotFoundException for j$.com.android.tools.r8.a at ML Kit init.

Inject the original classes2.dex (Java 8+ desugar libs) after apktool
build instead of using the recompiled version.

Co-authored-by: KaKi87 <KaKi87@pm.me>

* Fix missing drawable resources from density split APKs

The base APK is an app bundle module; density-specific drawables like
seek_thumb_nor_b live in config.xhdpi.apk and were missing after rebuild,
causing Resources$NotFoundException at runtime.

- Merge non-9-patch resources from split APKs before apktool build
- Sync public.xml IDs from R smali only when backing files exist
- Add 713 density-specific resource IDs to public.xml

Co-authored-by: KaKi87 <KaKi87@pm.me>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-08-10 16:07:14 +02:00

224 lines
7.7 KiB
Java

package com.google.api.client.googleapis.media;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.util.Preconditions;
import com.google.common.base.MoreObjects;
import com.google.common.io.ByteStreams;
import java.io.InputStream;
import java.io.OutputStream;
/* loaded from: classes3.dex */
public final class MediaHttpDownloader {
public static final int MAXIMUM_CHUNK_SIZE = 33554432;
private long bytesDownloaded;
private long mediaContentLength;
private MediaHttpDownloaderProgressListener progressListener;
private final HttpRequestFactory requestFactory;
private final HttpTransport transport;
private boolean directDownloadEnabled = false;
private int chunkSize = MAXIMUM_CHUNK_SIZE;
private DownloadState downloadState = DownloadState.NOT_STARTED;
private long lastBytePos = -1;
/* loaded from: classes3.dex */
public enum DownloadState {
NOT_STARTED,
MEDIA_IN_PROGRESS,
MEDIA_COMPLETE
}
public MediaHttpDownloader(HttpTransport httpTransport, HttpRequestInitializer httpRequestInitializer) {
HttpRequestFactory createRequestFactory;
this.transport = (HttpTransport) Preconditions.checkNotNull(httpTransport);
if (httpRequestInitializer == null) {
createRequestFactory = httpTransport.createRequestFactory();
} else {
createRequestFactory = httpTransport.createRequestFactory(httpRequestInitializer);
}
this.requestFactory = createRequestFactory;
}
private HttpResponse executeCurrentRequest(long j, GenericUrl genericUrl, HttpHeaders httpHeaders, OutputStream outputStream) {
HttpRequest buildGetRequest = this.requestFactory.buildGetRequest(genericUrl);
if (httpHeaders != null) {
buildGetRequest.getHeaders().putAll(httpHeaders);
}
if (this.bytesDownloaded != 0 || j != -1) {
StringBuilder sb = new StringBuilder("bytes=");
sb.append(this.bytesDownloaded);
sb.append("-");
if (j != -1) {
sb.append(j);
}
buildGetRequest.getHeaders().setRange(sb.toString());
}
HttpResponse execute = buildGetRequest.execute();
try {
InputStream content = execute.getContent();
int i = ByteStreams.f12470a;
content.getClass();
outputStream.getClass();
byte[] bArr = new byte[8192];
while (true) {
int read = content.read(bArr);
if (read == -1) {
return execute;
}
outputStream.write(bArr, 0, read);
}
} finally {
execute.disconnect();
}
}
private long getNextByteIndex(String str) {
if (str == null) {
return 0L;
}
return Long.parseLong(str.substring(str.indexOf(45) + 1, str.indexOf(47))) + 1;
}
private void setMediaContentLength(String str) {
if (str != null && this.mediaContentLength == 0) {
this.mediaContentLength = Long.parseLong(str.substring(str.indexOf(47) + 1));
}
}
private void updateStateAndNotifyListener(DownloadState downloadState) {
this.downloadState = downloadState;
MediaHttpDownloaderProgressListener mediaHttpDownloaderProgressListener = this.progressListener;
if (mediaHttpDownloaderProgressListener != null) {
mediaHttpDownloaderProgressListener.progressChanged(this);
}
}
public void download(GenericUrl genericUrl, OutputStream outputStream) {
download(genericUrl, null, outputStream);
}
public int getChunkSize() {
return this.chunkSize;
}
public DownloadState getDownloadState() {
return this.downloadState;
}
public long getLastBytePosition() {
return this.lastBytePos;
}
public long getNumBytesDownloaded() {
return this.bytesDownloaded;
}
public double getProgress() {
long j = this.mediaContentLength;
if (j == 0) {
return 0.0d;
}
return this.bytesDownloaded / j;
}
public MediaHttpDownloaderProgressListener getProgressListener() {
return this.progressListener;
}
public HttpTransport getTransport() {
return this.transport;
}
public boolean isDirectDownloadEnabled() {
return this.directDownloadEnabled;
}
public MediaHttpDownloader setBytesDownloaded(long j) {
boolean z;
if (j >= 0) {
z = true;
} else {
z = false;
}
Preconditions.checkArgument(z);
this.bytesDownloaded = j;
return this;
}
public MediaHttpDownloader setChunkSize(int i) {
boolean z;
if (i > 0 && i <= 33554432) {
z = true;
} else {
z = false;
}
Preconditions.checkArgument(z);
this.chunkSize = i;
return this;
}
public MediaHttpDownloader setContentRange(long j, long j2) {
Preconditions.checkArgument(j2 >= j);
setBytesDownloaded(j);
this.lastBytePos = j2;
return this;
}
public MediaHttpDownloader setDirectDownloadEnabled(boolean z) {
this.directDownloadEnabled = z;
return this;
}
public MediaHttpDownloader setProgressListener(MediaHttpDownloaderProgressListener mediaHttpDownloaderProgressListener) {
this.progressListener = mediaHttpDownloaderProgressListener;
return this;
}
public void download(GenericUrl genericUrl, HttpHeaders httpHeaders, OutputStream outputStream) {
Preconditions.checkArgument(this.downloadState == DownloadState.NOT_STARTED);
genericUrl.put("alt", "media");
if (this.directDownloadEnabled) {
updateStateAndNotifyListener(DownloadState.MEDIA_IN_PROGRESS);
long longValue = ((Long) MoreObjects.a(executeCurrentRequest(this.lastBytePos, genericUrl, httpHeaders, outputStream).getHeaders().getContentLength(), Long.valueOf(this.mediaContentLength))).longValue();
this.mediaContentLength = longValue;
this.bytesDownloaded = longValue;
updateStateAndNotifyListener(DownloadState.MEDIA_COMPLETE);
return;
}
while (true) {
long j = (this.bytesDownloaded + this.chunkSize) - 1;
long j2 = this.lastBytePos;
if (j2 != -1) {
j = Math.min(j2, j);
}
String contentRange = executeCurrentRequest(j, genericUrl, httpHeaders, outputStream).getHeaders().getContentRange();
long nextByteIndex = getNextByteIndex(contentRange);
setMediaContentLength(contentRange);
long j3 = this.lastBytePos;
if (j3 != -1 && j3 <= nextByteIndex) {
this.bytesDownloaded = j3;
updateStateAndNotifyListener(DownloadState.MEDIA_COMPLETE);
return;
}
long j4 = this.mediaContentLength;
if (j4 <= nextByteIndex) {
this.bytesDownloaded = j4;
updateStateAndNotifyListener(DownloadState.MEDIA_COMPLETE);
return;
} else {
this.bytesDownloaded = nextByteIndex;
updateStateAndNotifyListener(DownloadState.MEDIA_IN_PROGRESS);
}
}
}
@Deprecated
public MediaHttpDownloader setContentRange(long j, int i) {
return setContentRange(j, i);
}
}