soul-browser/sources/java/com/google/api/client/googleapis/media/MediaHttpUploader.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

465 lines
18 KiB
Java

package com.google.api.client.googleapis.media;
import com.google.api.client.googleapis.MethodOverride;
import com.google.api.client.http.AbstractInputStreamContent;
import com.google.api.client.http.ByteArrayContent;
import com.google.api.client.http.EmptyContent;
import com.google.api.client.http.GZipEncoding;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpContent;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpMethods;
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.http.InputStreamContent;
import com.google.api.client.http.MultipartContent;
import com.google.api.client.util.Beta;
import com.google.api.client.util.ByteStreams;
import com.google.api.client.util.Preconditions;
import com.google.api.client.util.Sleeper;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.util.Arrays;
/* loaded from: classes3.dex */
public final class MediaHttpUploader {
public static final String CONTENT_LENGTH_HEADER = "X-Upload-Content-Length";
public static final String CONTENT_TYPE_HEADER = "X-Upload-Content-Type";
public static final int DEFAULT_CHUNK_SIZE = 10485760;
private static final int KB = 1024;
static final int MB = 1048576;
public static final int MINIMUM_CHUNK_SIZE = 262144;
private Byte cachedByte;
private InputStream contentInputStream;
private int currentChunkLength;
private HttpRequest currentRequest;
private byte[] currentRequestContentBuffer;
private boolean directUploadEnabled;
private boolean disableGZipContent;
private boolean isMediaContentLengthCalculated;
private final AbstractInputStreamContent mediaContent;
private long mediaContentLength;
private HttpContent metadata;
private MediaHttpUploaderProgressListener progressListener;
private final HttpRequestFactory requestFactory;
private long totalBytesClientSent;
private long totalBytesServerReceived;
private final HttpTransport transport;
private UploadState uploadState = UploadState.NOT_STARTED;
private String initiationRequestMethod = HttpMethods.POST;
private HttpHeaders initiationHeaders = new HttpHeaders();
String mediaContentLengthStr = "*";
private int chunkSize = DEFAULT_CHUNK_SIZE;
Sleeper sleeper = Sleeper.DEFAULT;
/* loaded from: classes3.dex */
public static class ContentChunk {
private final AbstractInputStreamContent content;
private final String contentRange;
public ContentChunk(AbstractInputStreamContent abstractInputStreamContent, String str) {
this.content = abstractInputStreamContent;
this.contentRange = str;
}
public AbstractInputStreamContent getContent() {
return this.content;
}
public String getContentRange() {
return this.contentRange;
}
}
/* loaded from: classes3.dex */
public enum UploadState {
NOT_STARTED,
INITIATION_STARTED,
INITIATION_COMPLETE,
MEDIA_IN_PROGRESS,
MEDIA_COMPLETE
}
public MediaHttpUploader(AbstractInputStreamContent abstractInputStreamContent, HttpTransport httpTransport, HttpRequestInitializer httpRequestInitializer) {
HttpRequestFactory createRequestFactory;
this.mediaContent = (AbstractInputStreamContent) Preconditions.checkNotNull(abstractInputStreamContent);
this.transport = (HttpTransport) Preconditions.checkNotNull(httpTransport);
if (httpRequestInitializer == null) {
createRequestFactory = httpTransport.createRequestFactory();
} else {
createRequestFactory = httpTransport.createRequestFactory(httpRequestInitializer);
}
this.requestFactory = createRequestFactory;
}
private ContentChunk buildContentChunk() {
int i;
int i2;
int i3;
AbstractInputStreamContent byteArrayContent;
String str;
if (isMediaLengthKnown()) {
i = (int) Math.min(this.chunkSize, getMediaContentLength() - this.totalBytesServerReceived);
} else {
i = this.chunkSize;
}
if (isMediaLengthKnown()) {
this.contentInputStream.mark(i);
long j = i;
byteArrayContent = new InputStreamContent(this.mediaContent.getType(), ByteStreams.limit(this.contentInputStream, j)).setRetrySupported(true).setLength(j).setCloseInputStream(false);
this.mediaContentLengthStr = String.valueOf(getMediaContentLength());
} else {
byte[] bArr = this.currentRequestContentBuffer;
if (bArr == null) {
Byte b = this.cachedByte;
if (b == null) {
i2 = i + 1;
} else {
i2 = i;
}
byte[] bArr2 = new byte[i + 1];
this.currentRequestContentBuffer = bArr2;
if (b != null) {
bArr2[0] = b.byteValue();
}
i3 = 0;
} else {
int i4 = (int) (this.totalBytesClientSent - this.totalBytesServerReceived);
System.arraycopy(bArr, this.currentChunkLength - i4, bArr, 0, i4);
Byte b2 = this.cachedByte;
if (b2 != null) {
this.currentRequestContentBuffer[i4] = b2.byteValue();
}
i2 = i - i4;
i3 = i4;
}
int read = ByteStreams.read(this.contentInputStream, this.currentRequestContentBuffer, (i + 1) - i2, i2);
if (read < i2) {
i = Math.max(0, read) + i3;
if (this.cachedByte != null) {
i++;
this.cachedByte = null;
}
if (this.mediaContentLengthStr.equals("*")) {
this.mediaContentLengthStr = String.valueOf(this.totalBytesServerReceived + i);
}
} else {
this.cachedByte = Byte.valueOf(this.currentRequestContentBuffer[i]);
}
byteArrayContent = new ByteArrayContent(this.mediaContent.getType(), this.currentRequestContentBuffer, 0, i);
this.totalBytesClientSent = this.totalBytesServerReceived + i;
}
this.currentChunkLength = i;
if (i == 0) {
str = "bytes */" + this.mediaContentLengthStr;
} else {
str = "bytes " + this.totalBytesServerReceived + "-" + ((this.totalBytesServerReceived + i) - 1) + "/" + this.mediaContentLengthStr;
}
return new ContentChunk(byteArrayContent, str);
}
private HttpResponse directUpload(GenericUrl genericUrl) {
updateStateAndNotifyListener(UploadState.MEDIA_IN_PROGRESS);
HttpContent httpContent = this.mediaContent;
if (this.metadata != null) {
httpContent = new MultipartContent().setContentParts(Arrays.asList(this.metadata, this.mediaContent));
genericUrl.put("uploadType", "multipart");
} else {
genericUrl.put("uploadType", "media");
}
HttpRequest buildRequest = this.requestFactory.buildRequest(this.initiationRequestMethod, genericUrl, httpContent);
buildRequest.getHeaders().putAll(this.initiationHeaders);
HttpResponse executeCurrentRequest = executeCurrentRequest(buildRequest);
try {
if (isMediaLengthKnown()) {
this.totalBytesServerReceived = getMediaContentLength();
}
updateStateAndNotifyListener(UploadState.MEDIA_COMPLETE);
return executeCurrentRequest;
} catch (Throwable th) {
executeCurrentRequest.disconnect();
throw th;
}
}
private HttpResponse executeCurrentRequest(HttpRequest httpRequest) {
if (!this.disableGZipContent && !(httpRequest.getContent() instanceof EmptyContent)) {
httpRequest.setEncoding(new GZipEncoding());
}
return executeCurrentRequestWithoutGZip(httpRequest);
}
private HttpResponse executeCurrentRequestWithoutGZip(HttpRequest httpRequest) {
new MethodOverride().intercept(httpRequest);
httpRequest.setThrowExceptionOnExecuteError(false);
return httpRequest.execute();
}
private HttpResponse executeUploadInitiation(GenericUrl genericUrl) {
updateStateAndNotifyListener(UploadState.INITIATION_STARTED);
genericUrl.put("uploadType", "resumable");
HttpContent httpContent = this.metadata;
if (httpContent == null) {
httpContent = new EmptyContent();
}
HttpRequest buildRequest = this.requestFactory.buildRequest(this.initiationRequestMethod, genericUrl, httpContent);
this.initiationHeaders.set(CONTENT_TYPE_HEADER, (Object) this.mediaContent.getType());
if (isMediaLengthKnown()) {
this.initiationHeaders.set(CONTENT_LENGTH_HEADER, (Object) Long.valueOf(getMediaContentLength()));
}
buildRequest.getHeaders().putAll(this.initiationHeaders);
HttpResponse executeCurrentRequest = executeCurrentRequest(buildRequest);
try {
updateStateAndNotifyListener(UploadState.INITIATION_COMPLETE);
return executeCurrentRequest;
} catch (Throwable th) {
executeCurrentRequest.disconnect();
throw th;
}
}
private long getMediaContentLength() {
if (!this.isMediaContentLengthCalculated) {
this.mediaContentLength = this.mediaContent.getLength();
this.isMediaContentLengthCalculated = true;
}
return this.mediaContentLength;
}
private long getNextByteIndex(String str) {
if (str == null) {
return 0L;
}
return Long.parseLong(str.substring(str.indexOf(45) + 1)) + 1;
}
private boolean isMediaLengthKnown() {
if (getMediaContentLength() >= 0) {
return true;
}
return false;
}
private HttpResponse resumableUpload(GenericUrl genericUrl) {
HttpResponse executeCurrentRequest;
boolean z;
HttpResponse executeUploadInitiation = executeUploadInitiation(genericUrl);
if (!executeUploadInitiation.isSuccessStatusCode()) {
return executeUploadInitiation;
}
try {
GenericUrl genericUrl2 = new GenericUrl(executeUploadInitiation.getHeaders().getLocation());
executeUploadInitiation.disconnect();
InputStream inputStream = this.mediaContent.getInputStream();
this.contentInputStream = inputStream;
if (!inputStream.markSupported() && isMediaLengthKnown()) {
this.contentInputStream = new BufferedInputStream(this.contentInputStream);
}
while (true) {
ContentChunk buildContentChunk = buildContentChunk();
HttpRequest buildPutRequest = this.requestFactory.buildPutRequest(genericUrl2, null);
this.currentRequest = buildPutRequest;
buildPutRequest.setContent(buildContentChunk.getContent());
this.currentRequest.getHeaders().setContentRange(buildContentChunk.getContentRange());
new MediaUploadErrorHandler(this, this.currentRequest);
if (isMediaLengthKnown()) {
executeCurrentRequest = executeCurrentRequestWithoutGZip(this.currentRequest);
} else {
executeCurrentRequest = executeCurrentRequest(this.currentRequest);
}
try {
if (executeCurrentRequest.isSuccessStatusCode()) {
this.totalBytesServerReceived = getMediaContentLength();
if (this.mediaContent.getCloseInputStream()) {
this.contentInputStream.close();
}
updateStateAndNotifyListener(UploadState.MEDIA_COMPLETE);
return executeCurrentRequest;
}
if (executeCurrentRequest.getStatusCode() != 308) {
if (this.mediaContent.getCloseInputStream()) {
this.contentInputStream.close();
}
return executeCurrentRequest;
}
String location = executeCurrentRequest.getHeaders().getLocation();
if (location != null) {
genericUrl2 = new GenericUrl(location);
}
long nextByteIndex = getNextByteIndex(executeCurrentRequest.getHeaders().getRange());
long j = nextByteIndex - this.totalBytesServerReceived;
boolean z2 = false;
if (j >= 0 && j <= this.currentChunkLength) {
z = true;
} else {
z = false;
}
Preconditions.checkState(z);
long j2 = this.currentChunkLength - j;
if (isMediaLengthKnown()) {
if (j2 > 0) {
this.contentInputStream.reset();
if (j == this.contentInputStream.skip(j)) {
z2 = true;
}
Preconditions.checkState(z2);
}
} else if (j2 == 0) {
this.currentRequestContentBuffer = null;
}
this.totalBytesServerReceived = nextByteIndex;
updateStateAndNotifyListener(UploadState.MEDIA_IN_PROGRESS);
executeCurrentRequest.disconnect();
} catch (Throwable th) {
executeCurrentRequest.disconnect();
throw th;
}
}
} catch (Throwable th2) {
executeUploadInitiation.disconnect();
throw th2;
}
}
private void updateStateAndNotifyListener(UploadState uploadState) {
this.uploadState = uploadState;
MediaHttpUploaderProgressListener mediaHttpUploaderProgressListener = this.progressListener;
if (mediaHttpUploaderProgressListener != null) {
mediaHttpUploaderProgressListener.progressChanged(this);
}
}
public int getChunkSize() {
return this.chunkSize;
}
public boolean getDisableGZipContent() {
return this.disableGZipContent;
}
public HttpHeaders getInitiationHeaders() {
return this.initiationHeaders;
}
public String getInitiationRequestMethod() {
return this.initiationRequestMethod;
}
public HttpContent getMediaContent() {
return this.mediaContent;
}
public HttpContent getMetadata() {
return this.metadata;
}
public long getNumBytesUploaded() {
return this.totalBytesServerReceived;
}
public double getProgress() {
Preconditions.checkArgument(isMediaLengthKnown(), "Cannot call getProgress() if the specified AbstractInputStreamContent has no content length. Use getNumBytesUploaded() to denote progress instead.");
if (getMediaContentLength() == 0) {
return 0.0d;
}
return this.totalBytesServerReceived / getMediaContentLength();
}
public MediaHttpUploaderProgressListener getProgressListener() {
return this.progressListener;
}
public Sleeper getSleeper() {
return this.sleeper;
}
public HttpTransport getTransport() {
return this.transport;
}
public UploadState getUploadState() {
return this.uploadState;
}
public boolean isDirectUploadEnabled() {
return this.directUploadEnabled;
}
@Beta
public void serverErrorCallback() {
Preconditions.checkNotNull(this.currentRequest, "The current request should not be null");
this.currentRequest.setContent(new EmptyContent());
this.currentRequest.getHeaders().setContentRange("bytes */" + this.mediaContentLengthStr);
}
public MediaHttpUploader setChunkSize(int i) {
boolean z;
if (i > 0 && i % MINIMUM_CHUNK_SIZE == 0) {
z = true;
} else {
z = false;
}
Preconditions.checkArgument(z, "chunkSize must be a positive multiple of 262144.");
this.chunkSize = i;
return this;
}
public MediaHttpUploader setDirectUploadEnabled(boolean z) {
this.directUploadEnabled = z;
return this;
}
public MediaHttpUploader setDisableGZipContent(boolean z) {
this.disableGZipContent = z;
return this;
}
public MediaHttpUploader setInitiationHeaders(HttpHeaders httpHeaders) {
this.initiationHeaders = httpHeaders;
return this;
}
public MediaHttpUploader setInitiationRequestMethod(String str) {
boolean z;
if (!str.equals(HttpMethods.POST) && !str.equals(HttpMethods.PUT) && !str.equals(HttpMethods.PATCH)) {
z = false;
} else {
z = true;
}
Preconditions.checkArgument(z);
this.initiationRequestMethod = str;
return this;
}
public MediaHttpUploader setMetadata(HttpContent httpContent) {
this.metadata = httpContent;
return this;
}
public MediaHttpUploader setProgressListener(MediaHttpUploaderProgressListener mediaHttpUploaderProgressListener) {
this.progressListener = mediaHttpUploaderProgressListener;
return this;
}
public MediaHttpUploader setSleeper(Sleeper sleeper) {
this.sleeper = sleeper;
return this;
}
public HttpResponse upload(GenericUrl genericUrl) {
boolean z;
if (this.uploadState == UploadState.NOT_STARTED) {
z = true;
} else {
z = false;
}
Preconditions.checkArgument(z);
if (this.directUploadEnabled) {
return directUpload(genericUrl);
}
return resumableUpload(genericUrl);
}
}