mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Garmin: Handle http requests async
This commit is contained in:
+1
-1
@@ -6,5 +6,5 @@ import nodomain.freeyourgadget.internethelper.aidl.http.IHttpCallback;
|
||||
interface IHttpService {
|
||||
int version();
|
||||
|
||||
void get(in HttpRequest request, IHttpCallback cb);
|
||||
void send(in HttpRequest request, IHttpCallback cb);
|
||||
}
|
||||
|
||||
@@ -5,5 +5,4 @@ import nodomain.freeyourgadget.internethelper.aidl.wifi.IWifiCallback;
|
||||
interface IWifiService {
|
||||
int version();
|
||||
String getCurrentSsid();
|
||||
void connect(String ssid, String password, IWifiCallback cb);
|
||||
}
|
||||
|
||||
-1
@@ -169,7 +169,6 @@ public abstract class AbstractAppManagerFragment extends Fragment {
|
||||
"GET",
|
||||
Collections.emptyMap(),
|
||||
null,
|
||||
"application/json",
|
||||
false
|
||||
);
|
||||
JSONObject appEntry = (JSONObject) appstoreEntry.getJSONArray("data").get(0);
|
||||
|
||||
+1
-1
@@ -909,7 +909,7 @@ public class BangleJSDeviceSupport extends AbstractBTLESingleDeviceSupport {
|
||||
}
|
||||
if (headers == null) headers = emptyMap();
|
||||
|
||||
String response = InternetUtils.Companion.doStringRequest(Uri.parse(url), method, headers, body, "application/json", insecure);
|
||||
String response = InternetUtils.Companion.doStringRequest(Uri.parse(url), method, headers, body, insecure);
|
||||
JSONObject o = new JSONObject();
|
||||
String _xmlPath = "";
|
||||
String _xmlReturn = "";
|
||||
|
||||
+9
@@ -91,6 +91,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.IncomingFitDefinitionDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.MaxPacketSizeDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.NotificationSubscriptionDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.ProtobufResponseEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.SupportedFileTypesDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.WeatherRequestDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitAsyncProcessor;
|
||||
@@ -407,6 +408,14 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
||||
.build());
|
||||
sendOutgoingMessage("init realtime settings", realtimeSettingsInit);
|
||||
}
|
||||
} else if (deviceEvent instanceof ProtobufResponseEvent protobufResponseEvent) {
|
||||
sendOutgoingMessage(
|
||||
"protobuf response event for " + protobufResponseEvent.messageId,
|
||||
protocolBufferHandler.prepareProtobufResponse(
|
||||
protobufResponseEvent.payload,
|
||||
protobufResponseEvent.messageId
|
||||
)
|
||||
);
|
||||
} else if (deviceEvent instanceof NotificationSubscriptionDeviceEvent) {
|
||||
final boolean enable = ((NotificationSubscriptionDeviceEvent) deviceEvent).enable;
|
||||
notificationsHandler.setEnabled(enable);
|
||||
|
||||
+6
-7
@@ -56,8 +56,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http.HttpHand
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.ProtobufMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.ProtobufStatusMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.notifications.GBProgressNotification;
|
||||
import nodomain.freeyourgadget.gadgetbridge.webview.CurrentPosition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
@@ -133,11 +131,12 @@ public class ProtocolBufferHandler implements MessageHandler {
|
||||
return prepareProtobufResponse(processProtobufSmsNotificationMessage(smart.getSmsNotificationService()), message.getRequestId());
|
||||
}
|
||||
if (smart.hasHttpService()) {
|
||||
final GdiHttpService.HttpService response = httpHandler.handle(smart.getHttpService());
|
||||
if (response == null) {
|
||||
return null;
|
||||
final GdiHttpService.HttpService response = httpHandler.handle(smart.getHttpService(), message.getRequestId());
|
||||
if (response != null) {
|
||||
return prepareProtobufResponse(GdiSmartProto.Smart.newBuilder().setHttpService(response).build(), message.getRequestId());
|
||||
}
|
||||
return prepareProtobufResponse(GdiSmartProto.Smart.newBuilder().setHttpService(response).build(), message.getRequestId());
|
||||
processed = true;
|
||||
// Response will be async
|
||||
}
|
||||
if (smart.hasDataTransferService()) {
|
||||
final GdiDataTransferService.DataTransferService response = dataTransferHandler.handle(smart.getDataTransferService(), message.getRequestId());
|
||||
@@ -619,7 +618,7 @@ public class ProtocolBufferHandler implements MessageHandler {
|
||||
return prepareProtobufMessage(protobufPayload.toByteArray(), GFDIMessage.GarminMessage.PROTOBUF_REQUEST, requestId);
|
||||
}
|
||||
|
||||
private ProtobufMessage prepareProtobufResponse(GdiSmartProto.Smart protobufPayload, int requestId) {
|
||||
public ProtobufMessage prepareProtobufResponse(GdiSmartProto.Smart protobufPayload, int requestId) {
|
||||
if (null == protobufPayload)
|
||||
return null;
|
||||
return prepareProtobufMessage(protobufPayload.toByteArray(), GFDIMessage.GarminMessage.PROTOBUF_RESPONSE, requestId);
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiSmartProto;
|
||||
|
||||
public class ProtobufResponseEvent extends GBDeviceEvent {
|
||||
public GdiSmartProto.Smart payload;
|
||||
public int messageId;
|
||||
|
||||
public ProtobufResponseEvent(final GdiSmartProto.Smart payload, final int messageId) {
|
||||
this.payload = payload;
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluate(@NonNull final Context context, @NonNull final GBDevice device) {
|
||||
// Handled in support class
|
||||
}
|
||||
}
|
||||
+16
-2
@@ -18,11 +18,15 @@ public class GarminHttpRequest {
|
||||
private final GdiHttpService.HttpService.RawRequest rawRequest;
|
||||
private final GdiHttpService.HttpService.WebRequest webRequest;
|
||||
|
||||
private final int messageRequestId;
|
||||
|
||||
private final String method;
|
||||
private final Uri uri;
|
||||
private final Map<String, String> headers;
|
||||
|
||||
public GarminHttpRequest(final GdiHttpService.HttpService.RawRequest rawRequest) {
|
||||
public GarminHttpRequest(final GdiHttpService.HttpService.RawRequest rawRequest,
|
||||
final int messageRequestId) {
|
||||
this.messageRequestId = messageRequestId;
|
||||
this.rawRequest = rawRequest;
|
||||
this.webRequest = null;
|
||||
this.method = rawRequest.getMethod().name();
|
||||
@@ -30,7 +34,9 @@ public class GarminHttpRequest {
|
||||
this.headers = headersToMap(rawRequest.getHeaderList());
|
||||
}
|
||||
|
||||
public GarminHttpRequest(final GdiHttpService.HttpService.WebRequest webRequest) throws GarminJsonException {
|
||||
public GarminHttpRequest(final GdiHttpService.HttpService.WebRequest webRequest,
|
||||
final int messageRequestId) throws GarminJsonException {
|
||||
this.messageRequestId = messageRequestId;
|
||||
this.rawRequest = null;
|
||||
this.webRequest = webRequest;
|
||||
this.method = webRequest.getMethod().name();
|
||||
@@ -44,10 +50,18 @@ public class GarminHttpRequest {
|
||||
}
|
||||
}
|
||||
|
||||
public int getMessageRequestId() {
|
||||
return messageRequestId;
|
||||
}
|
||||
|
||||
public GdiHttpService.HttpService.RawRequest getRawRequest() {
|
||||
return rawRequest;
|
||||
}
|
||||
|
||||
public GdiHttpService.HttpService.WebRequest getWebRequest() {
|
||||
return webRequest;
|
||||
}
|
||||
|
||||
public Uri getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
+9
@@ -5,6 +5,7 @@ import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class GarminHttpResponse {
|
||||
private boolean complete = true;
|
||||
private int status = 200;
|
||||
private final Map<String, String> headers = new LinkedHashMap<>();
|
||||
private byte[] body = new byte[0];
|
||||
@@ -14,6 +15,14 @@ public class GarminHttpResponse {
|
||||
public GarminHttpResponse() {
|
||||
}
|
||||
|
||||
public boolean isComplete() {
|
||||
return complete;
|
||||
}
|
||||
|
||||
public void setComplete(final boolean complete) {
|
||||
this.complete = complete;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
+86
-11
@@ -51,9 +51,9 @@ public class HttpHandler {
|
||||
);
|
||||
}
|
||||
|
||||
public GdiHttpService.HttpService handle(final GdiHttpService.HttpService httpService) {
|
||||
public GdiHttpService.HttpService handle(final GdiHttpService.HttpService httpService, final int messageRequestId) {
|
||||
if (httpService.hasRawRequest()) {
|
||||
final GdiHttpService.HttpService.RawResponse rawResponse = handleRawRequest(httpService.getRawRequest());
|
||||
final GdiHttpService.HttpService.RawResponse rawResponse = handleRawRequest(httpService.getRawRequest(), messageRequestId);
|
||||
if (rawResponse != null) {
|
||||
return GdiHttpService.HttpService.newBuilder()
|
||||
.setRawResponse(rawResponse)
|
||||
@@ -61,7 +61,7 @@ public class HttpHandler {
|
||||
}
|
||||
return null;
|
||||
} else if (httpService.hasWebRequest()) {
|
||||
final GdiHttpService.HttpService.WebResponse webResponse = handleWebRequest(httpService.getWebRequest());
|
||||
final GdiHttpService.HttpService.WebResponse webResponse = handleWebRequest(httpService.getWebRequest(), messageRequestId);
|
||||
if (webResponse != null) {
|
||||
return GdiHttpService.HttpService.newBuilder()
|
||||
.setWebResponse(webResponse)
|
||||
@@ -75,10 +75,11 @@ public class HttpHandler {
|
||||
return null;
|
||||
}
|
||||
|
||||
public GdiHttpService.HttpService.RawResponse handleRawRequest(final GdiHttpService.HttpService.RawRequest rawRequest) {
|
||||
LOG.debug("Got rawRequest: {} - {}", rawRequest.getMethod(), rawRequest.getUrl());
|
||||
public GdiHttpService.HttpService.RawResponse handleRawRequest(final GdiHttpService.HttpService.RawRequest rawRequest,
|
||||
final int messageRequestId) {
|
||||
LOG.debug("Got rawRequest {}: {} - {}", messageRequestId, rawRequest.getMethod(), rawRequest.getUrl());
|
||||
|
||||
final GarminHttpRequest request = new GarminHttpRequest(rawRequest);
|
||||
final GarminHttpRequest request = new GarminHttpRequest(rawRequest, messageRequestId);
|
||||
|
||||
final GarminHttpResponse response = handleRequest(request);
|
||||
|
||||
@@ -88,6 +89,11 @@ public class HttpHandler {
|
||||
.build();
|
||||
}
|
||||
|
||||
if (!response.isComplete()) {
|
||||
// Async response
|
||||
return null;
|
||||
}
|
||||
|
||||
return createRawResponse(request, response);
|
||||
}
|
||||
|
||||
@@ -106,14 +112,19 @@ public class HttpHandler {
|
||||
LOG.debug("Handling request to {}", interceptor.getClass().getSimpleName());
|
||||
final GarminHttpResponse response = interceptor.handle(request);
|
||||
if (response != null) {
|
||||
LOG.debug("Response from interceptor: {}, {} bytes", response.getStatus(), response.getBody().length);
|
||||
LOG.debug(
|
||||
"Response from interceptor: {}, {} bytes, complete={}",
|
||||
response.getStatus(),
|
||||
response.getBody().length,
|
||||
response.isComplete()
|
||||
);
|
||||
} else {
|
||||
LOG.debug("No response from interceptor");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private static GdiHttpService.HttpService.RawResponse createRawResponse(
|
||||
public static GdiHttpService.HttpService.RawResponse createRawResponse(
|
||||
final GarminHttpRequest request,
|
||||
final GarminHttpResponse response
|
||||
) {
|
||||
@@ -178,11 +189,12 @@ public class HttpHandler {
|
||||
.build();
|
||||
}
|
||||
|
||||
public GdiHttpService.HttpService.WebResponse handleWebRequest(final GdiHttpService.HttpService.WebRequest webRequest) {
|
||||
LOG.debug("Got webRequest: {} {}", webRequest.getMethod(), webRequest.getUrl());
|
||||
public GdiHttpService.HttpService.WebResponse handleWebRequest(final GdiHttpService.HttpService.WebRequest webRequest,
|
||||
final int messageRequestId) {
|
||||
LOG.debug("Got webRequest {}: {} {}", messageRequestId, webRequest.getMethod(), webRequest.getUrl());
|
||||
|
||||
try {
|
||||
final GarminHttpRequest request = new GarminHttpRequest(webRequest);
|
||||
final GarminHttpRequest request = new GarminHttpRequest(webRequest, messageRequestId);
|
||||
|
||||
final GarminHttpResponse response = handleRequest(request);
|
||||
|
||||
@@ -193,6 +205,69 @@ public class HttpHandler {
|
||||
.build();
|
||||
}
|
||||
|
||||
if (!response.isComplete()) {
|
||||
// Async response
|
||||
return null;
|
||||
}
|
||||
|
||||
return createWebResponse(request, response);
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Failed to create web response", e);
|
||||
return GdiHttpService.HttpService.WebResponse.newBuilder()
|
||||
.setStatus(GdiHttpService.HttpService.Status.UNKNOWN_STATUS)
|
||||
.setHttpStatus(0)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
public static GdiHttpService.HttpService createSuccessResponse(final GarminHttpRequest request,
|
||||
final GarminHttpResponse response) {
|
||||
if (request.getRawRequest() != null) {
|
||||
final GdiHttpService.HttpService.RawResponse rawResponse = createRawResponse(request, response);
|
||||
if (rawResponse != null) {
|
||||
return GdiHttpService.HttpService.newBuilder()
|
||||
.setRawResponse(rawResponse)
|
||||
.build();
|
||||
}
|
||||
return null;
|
||||
} else if (request.getWebRequest() != null) {
|
||||
final GdiHttpService.HttpService.WebResponse webResponse = createWebResponse(request, response);
|
||||
if (webResponse != null) {
|
||||
return GdiHttpService.HttpService.newBuilder()
|
||||
.setWebResponse(webResponse)
|
||||
.build();
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Should never happen");
|
||||
}
|
||||
}
|
||||
|
||||
public static GdiHttpService.HttpService createErrorResponse(final GarminHttpRequest request) {
|
||||
if (request.getRawRequest() != null) {
|
||||
return GdiHttpService.HttpService.newBuilder()
|
||||
.setRawResponse(GdiHttpService.HttpService.RawResponse.newBuilder()
|
||||
.setStatus(GdiHttpService.HttpService.Status.UNKNOWN_STATUS)
|
||||
.build())
|
||||
.build();
|
||||
} else if (request.getWebRequest() != null) {
|
||||
return GdiHttpService.HttpService.newBuilder()
|
||||
.setWebResponse(GdiHttpService.HttpService.WebResponse.newBuilder()
|
||||
.setStatus(GdiHttpService.HttpService.Status.UNKNOWN_STATUS)
|
||||
.setHttpStatus(0)
|
||||
.build())
|
||||
.build();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Should never happen");
|
||||
}
|
||||
}
|
||||
|
||||
public static GdiHttpService.HttpService.WebResponse createWebResponse(
|
||||
final GarminHttpRequest request,
|
||||
final GarminHttpResponse response
|
||||
) {
|
||||
final GdiHttpService.HttpService.WebRequest webRequest = request.getWebRequest();
|
||||
try {
|
||||
final byte[] headers;
|
||||
if (webRequest.getHttpHeadersInResponse()) {
|
||||
byte[] encodedHeaders = null;
|
||||
|
||||
+83
-31
@@ -1,7 +1,6 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http.interceptors;
|
||||
|
||||
import android.os.RemoteException;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -11,24 +10,31 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.internet.InternetFirewall;
|
||||
import nodomain.freeyourgadget.gadgetbridge.internet.InternetRequestType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiHttpService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiSmartProto;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.ProtobufResponseEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http.GarminHttpRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http.GarminHttpResponse;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http.HttpHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.InternetHelperSingleton;
|
||||
import nodomain.freeyourgadget.internethelper.aidl.http.HttpRequest;
|
||||
import nodomain.freeyourgadget.internethelper.aidl.http.HttpResponse;
|
||||
import nodomain.freeyourgadget.internethelper.aidl.http.IHttpCallback;
|
||||
import nodomain.freeyourgadget.internethelper.aidl.http.IHttpService;
|
||||
|
||||
@SuppressWarnings("ClassCanBeRecord")
|
||||
public class FirewallInterceptor implements HttpInterceptor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FirewallInterceptor.class);
|
||||
|
||||
private final InternetFirewall firewall;
|
||||
private final GarminSupport deviceSupport;
|
||||
|
||||
public FirewallInterceptor(final GarminSupport deviceSupport) {
|
||||
this.deviceSupport = deviceSupport;
|
||||
this.firewall = new InternetFirewall(InternetRequestType.WATCH_APP, deviceSupport.getDevice());
|
||||
}
|
||||
|
||||
@@ -59,44 +65,90 @@ public class FirewallInterceptor implements HttpInterceptor {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO refactor this to be asynchronous
|
||||
final WebResourceResponse response;
|
||||
try {
|
||||
response = InternetHelperSingleton.INSTANCE.send(
|
||||
request.getUri(),
|
||||
HttpRequest.Method.valueOf(request.getMethod()),
|
||||
request.getHeaders(),
|
||||
new String(request.getBody(), StandardCharsets.UTF_8), // FIXME we should be able to send the raw bytes
|
||||
"application/json",
|
||||
false
|
||||
);
|
||||
} catch (final RemoteException e) {
|
||||
LOG.error("Remote exception", e);
|
||||
return null;
|
||||
} catch (final InterruptedException e) {
|
||||
LOG.error("Request was interrupted", e);
|
||||
final IHttpService httpService = InternetHelperSingleton.INSTANCE.getHttpService();
|
||||
if (httpService == null) {
|
||||
LOG.error("HttpService is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
LOG.error("Response is null");
|
||||
LOG.debug("Forwarding http request {} to InternetHelper", request.getMessageRequestId());
|
||||
|
||||
try {
|
||||
final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
|
||||
final ParcelFileDescriptor pipeRead = pipe[0];
|
||||
final ParcelFileDescriptor pipeWrite = pipe[1];
|
||||
|
||||
final HttpRequest httpRequest = new HttpRequest(
|
||||
request.getUri().toString(),
|
||||
HttpRequest.Method.valueOf(request.getMethod()),
|
||||
request.getHeaders(),
|
||||
pipeRead,
|
||||
false
|
||||
);
|
||||
|
||||
httpService.send(httpRequest, new IHttpCallback.Stub() {
|
||||
@Override
|
||||
public void onResponse(final HttpResponse response) {
|
||||
LOG.debug("Got response to {} from InternetHelper", request.getMessageRequestId());
|
||||
handleHttpResponse(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(final String message) {
|
||||
LOG.error("Http request for {} failed: {}", request.getMessageRequestId(), message);
|
||||
handleHttpException(request);
|
||||
}
|
||||
});
|
||||
|
||||
try (ParcelFileDescriptor.AutoCloseOutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(pipeWrite)) {
|
||||
out.write(request.getBody());
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Failed to send request to InternetHelper", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
final GarminHttpResponse garminHttpResponse = new GarminHttpResponse();
|
||||
garminHttpResponse.setStatus(response.getStatusCode());
|
||||
garminHttpResponse.getHeaders().putAll(response.getResponseHeaders());
|
||||
|
||||
try {
|
||||
garminHttpResponse.setBody(readAllBytes(response.getData()));
|
||||
} catch (final IOException e) {
|
||||
LOG.error("Failed to read bytes from response", e);
|
||||
return null;
|
||||
}
|
||||
garminHttpResponse.setComplete(false);
|
||||
|
||||
return garminHttpResponse;
|
||||
}
|
||||
|
||||
private void handleHttpResponse(@NotNull final GarminHttpRequest request,
|
||||
final HttpResponse response) {
|
||||
final GarminHttpResponse garminHttpResponse = new GarminHttpResponse();
|
||||
garminHttpResponse.setStatus(response.getStatus());
|
||||
garminHttpResponse.getHeaders().putAll(response.getHeaders());
|
||||
try (ParcelFileDescriptor.AutoCloseInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(response.getBody())) {
|
||||
garminHttpResponse.setBody(readAllBytes(in));
|
||||
} catch (final IOException e) {
|
||||
LOG.error("Failed to read bytes from response", e);
|
||||
handleHttpException(request);
|
||||
return;
|
||||
}
|
||||
|
||||
final GdiHttpService.HttpService successResponse = HttpHandler.createSuccessResponse(request, garminHttpResponse);
|
||||
if (successResponse != null) {
|
||||
sendHttpServiceRequest(request, successResponse);
|
||||
} else {
|
||||
final GdiHttpService.HttpService errorResponse = HttpHandler.createErrorResponse(request);
|
||||
sendHttpServiceRequest(request, errorResponse);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleHttpException(@NotNull final GarminHttpRequest request) {
|
||||
final GdiHttpService.HttpService errorResponse = HttpHandler.createErrorResponse(request);
|
||||
sendHttpServiceRequest(request, errorResponse);
|
||||
}
|
||||
|
||||
private void sendHttpServiceRequest(@NotNull final GarminHttpRequest request,
|
||||
@NotNull final GdiHttpService.HttpService httpService) {
|
||||
final GdiSmartProto.Smart smart = GdiSmartProto.Smart.newBuilder().setHttpService(httpService).build();
|
||||
deviceSupport.evaluateGBDeviceEvent(new ProtobufResponseEvent(
|
||||
smart, request.getMessageRequestId()
|
||||
));
|
||||
}
|
||||
|
||||
public static byte[] readAllBytes(final InputStream in) throws IOException {
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[4096];
|
||||
|
||||
+72
-41
@@ -27,7 +27,6 @@ import android.os.ParcelFileDescriptor
|
||||
import android.os.RemoteException
|
||||
import android.webkit.WebResourceResponse
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication
|
||||
import nodomain.freeyourgadget.internethelper.aidl.http.HttpHeaders
|
||||
import nodomain.freeyourgadget.internethelper.aidl.http.HttpRequest
|
||||
import nodomain.freeyourgadget.internethelper.aidl.http.HttpResponse
|
||||
import nodomain.freeyourgadget.internethelper.aidl.http.IHttpCallback
|
||||
@@ -58,7 +57,6 @@ object InternetHelperSingleton {
|
||||
}
|
||||
|
||||
fun getHttpService(): IHttpService? {
|
||||
ensureInternetHelperBound()
|
||||
return internetHelper
|
||||
}
|
||||
|
||||
@@ -89,50 +87,45 @@ object InternetHelperSingleton {
|
||||
fun send(
|
||||
webRequest: Uri,
|
||||
method: HttpRequest.Method,
|
||||
requestHeaders: Map<String, String>,
|
||||
body: String?,
|
||||
bodyContentType: String,
|
||||
allowInsecure: Boolean,
|
||||
requestHeaders: Map<String, String> = emptyMap(),
|
||||
body: ByteArray?,
|
||||
allowInsecure: Boolean = false,
|
||||
): WebResourceResponse? {
|
||||
if (internetHelper == null) {
|
||||
LOG.error("Internet helper is not available")
|
||||
return null
|
||||
}
|
||||
|
||||
val latch = CountDownLatch(1)
|
||||
var result: WebResourceResponse? = null
|
||||
val request = HttpRequest(webRequest.toString(), method, body, bodyContentType, allowInsecure, HttpHeaders(requestHeaders))
|
||||
|
||||
// Create pipe to stream request body
|
||||
val pipeRead: ParcelFileDescriptor?
|
||||
val pipeWrite: ParcelFileDescriptor?
|
||||
if (body != null) {
|
||||
val pipes = ParcelFileDescriptor.createPipe()
|
||||
pipeRead = pipes[0]
|
||||
pipeWrite = pipes[1]
|
||||
} else {
|
||||
pipeRead = null
|
||||
pipeWrite = null
|
||||
}
|
||||
|
||||
val request = HttpRequest(
|
||||
webRequest.toString(),
|
||||
method,
|
||||
requestHeaders,
|
||||
pipeRead,
|
||||
allowInsecure,
|
||||
)
|
||||
|
||||
LOG.debug("Forwarding {} request to {} to internet helper app", method.name, webRequest)
|
||||
|
||||
try {
|
||||
internetHelper?.get(request, object : IHttpCallback.Stub() {
|
||||
internetHelper?.send(request, object : IHttpCallback.Stub() {
|
||||
override fun onResponse(response: HttpResponse) {
|
||||
try {
|
||||
val headers = response.headers.toMap()
|
||||
val contentType = response.headers["content-type"]
|
||||
?.substringBefore(";")
|
||||
?.trim()
|
||||
?: "application/octet-stream"
|
||||
val encoding = response.headers["content-encoding"] ?: "UTF-8"
|
||||
val inputStream = ParcelFileDescriptor.AutoCloseInputStream(response.body)
|
||||
|
||||
result = if (contentType.equals("text/html", ignoreCase = true)) {
|
||||
val rawHtml = inputStream.bufferedReader(Charset.forName(encoding)).use { it.readText() }
|
||||
val cleanedHtml = Jsoup.parse(rawHtml).html()
|
||||
WebResourceResponse(
|
||||
contentType,
|
||||
encoding,
|
||||
response.status,
|
||||
"OK",
|
||||
headers,
|
||||
cleanedHtml.byteInputStream()
|
||||
)
|
||||
} else {
|
||||
WebResourceResponse(
|
||||
contentType,
|
||||
encoding,
|
||||
response.status,
|
||||
"OK",
|
||||
headers,
|
||||
inputStream
|
||||
)
|
||||
}
|
||||
|
||||
result = toWebResourceResponse(response)
|
||||
} catch (e: Exception) {
|
||||
LOG.error("Error processing HTTP response", e)
|
||||
} finally {
|
||||
@@ -141,14 +134,20 @@ object InternetHelperSingleton {
|
||||
}
|
||||
|
||||
override fun onException(message: String?) {
|
||||
LOG.error("Error during GET request: $message")
|
||||
LOG.error("Request failed: $message")
|
||||
result = null
|
||||
latch.countDown()
|
||||
}
|
||||
})
|
||||
|
||||
// Write the body to the pipe, if any
|
||||
if (pipeWrite != null) {
|
||||
ParcelFileDescriptor.AutoCloseOutputStream(pipeWrite).use { outputStream ->
|
||||
outputStream.write(body)
|
||||
}
|
||||
}
|
||||
} catch (e: RemoteException) {
|
||||
LOG.error("Error initiating GET request", e)
|
||||
LOG.error("Error sending request to InternetHelper", e)
|
||||
latch.countDown()
|
||||
return null
|
||||
}
|
||||
@@ -156,4 +155,36 @@ object InternetHelperSingleton {
|
||||
latch.await()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private fun toWebResourceResponse(response: HttpResponse): WebResourceResponse {
|
||||
val headers = response.headers.toMap()
|
||||
val contentType = response.headers["content-type"]
|
||||
?.substringBefore(";")
|
||||
?.trim()
|
||||
?: "application/octet-stream"
|
||||
val encoding = response.headers["content-encoding"] ?: "UTF-8"
|
||||
val inputStream = ParcelFileDescriptor.AutoCloseInputStream(response.body)
|
||||
|
||||
return if (contentType.equals("text/html", ignoreCase = true)) {
|
||||
val rawHtml = inputStream.bufferedReader(Charset.forName(encoding)).use { it.readText() }
|
||||
val cleanedHtml = Jsoup.parse(rawHtml).html()
|
||||
WebResourceResponse(
|
||||
contentType,
|
||||
encoding,
|
||||
response.status,
|
||||
"OK",
|
||||
headers,
|
||||
cleanedHtml.byteInputStream()
|
||||
)
|
||||
} else {
|
||||
WebResourceResponse(
|
||||
contentType,
|
||||
encoding,
|
||||
response.status,
|
||||
"OK",
|
||||
headers,
|
||||
inputStream
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.net.Uri
|
||||
import android.webkit.WebResourceResponse
|
||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig
|
||||
@@ -70,18 +71,16 @@ class InternetUtils {
|
||||
method: String = "GET",
|
||||
requestHeaders: Map<String, String> = emptyMap(),
|
||||
body: String? = null,
|
||||
bodyContentType: String = "text/plain",
|
||||
allowInsecure: Boolean = false
|
||||
): String? {
|
||||
val response: WebResourceResponse? = if (GBApplication.hasDirectInternetAccess()) {
|
||||
directRequest(uri, method, requestHeaders, body, bodyContentType, allowInsecure)
|
||||
directRequest(uri, method, requestHeaders, body, allowInsecure)
|
||||
} else {
|
||||
InternetHelperSingleton.send(
|
||||
uri,
|
||||
HttpRequest.Method.valueOf(method),
|
||||
headersWithUserAgent(requestHeaders),
|
||||
body,
|
||||
bodyContentType,
|
||||
body?.toByteArray(),
|
||||
allowInsecure,
|
||||
)
|
||||
}
|
||||
@@ -96,7 +95,6 @@ class InternetUtils {
|
||||
method: String = "GET",
|
||||
requestHeaders: Map<String, String> = emptyMap(),
|
||||
body: String? = null,
|
||||
bodyContentType: String = "application/json",
|
||||
allowInsecure: Boolean = false
|
||||
): JSONObject? {
|
||||
val text = doStringRequest(
|
||||
@@ -104,7 +102,6 @@ class InternetUtils {
|
||||
method,
|
||||
headersWithUserAgent(requestHeaders),
|
||||
body,
|
||||
bodyContentType,
|
||||
allowInsecure
|
||||
)
|
||||
try {
|
||||
@@ -127,7 +124,6 @@ class InternetUtils {
|
||||
method = "GET",
|
||||
requestHeaders = mapOf("User-Agent" to USER_AGENT),
|
||||
body = null,
|
||||
bodyContentType = "application/octet-stream",
|
||||
allowInsecure = false
|
||||
)
|
||||
} else {
|
||||
@@ -136,7 +132,6 @@ class InternetUtils {
|
||||
HttpRequest.Method.GET,
|
||||
requestHeaders = mapOf("User-Agent" to USER_AGENT),
|
||||
null,
|
||||
"application/octet-stream",
|
||||
false
|
||||
)
|
||||
}
|
||||
@@ -162,7 +157,6 @@ class InternetUtils {
|
||||
method: String,
|
||||
requestHeaders: Map<String, String>,
|
||||
body: String?,
|
||||
bodyContentType: String,
|
||||
allowInsecure: Boolean
|
||||
): WebResourceResponse {
|
||||
|
||||
@@ -178,7 +172,7 @@ class InternetUtils {
|
||||
// Convert body string to RequestBody if allowed
|
||||
val requestBody: RequestBody? =
|
||||
if (body != null && method.uppercase() !in listOf("GET", "HEAD")) {
|
||||
body.toRequestBody(bodyContentType.toMediaType())
|
||||
body.toRequestBody("application/octet-stream".toMediaType())
|
||||
} else null
|
||||
|
||||
// Configure HTTP method
|
||||
@@ -238,8 +232,11 @@ class InternetUtils {
|
||||
private fun createInsecureClient(): OkHttpClient {
|
||||
return try {
|
||||
val trustAllCerts = arrayOf<TrustManager>(
|
||||
@SuppressLint("CustomX509TrustManager")
|
||||
object : X509TrustManager {
|
||||
@SuppressLint("TrustAllX509TrustManager")
|
||||
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
|
||||
@SuppressLint("TrustAllX509TrustManager")
|
||||
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
|
||||
}
|
||||
@@ -259,4 +256,4 @@ class InternetUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
@@ -174,7 +175,7 @@ public class GBWebClient extends WebViewClient {
|
||||
body = getFirstPostForUrl(requestedUri.toString());
|
||||
LOG.debug("WEBVIEW POSTing with body: {}", body);
|
||||
}
|
||||
WebResourceResponse wrr = InternetHelperSingleton.INSTANCE.send(requestedUri, requestMethod, requestHeaders, body, "application/octet-stream", false);
|
||||
WebResourceResponse wrr = InternetHelperSingleton.INSTANCE.send(requestedUri, requestMethod, requestHeaders, body != null ? body.getBytes(StandardCharsets.UTF_8) : null, false);
|
||||
if (wrr != null && wrr.getStatusCode() < 400)
|
||||
return wrr;
|
||||
else
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
package nodomain.freeyourgadget.internethelper.aidl.http;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class HttpHeaders implements Parcelable {
|
||||
private final List<Pair<String, String>> headers = new ArrayList<>();
|
||||
|
||||
protected HttpHeaders(final Parcel in) {
|
||||
final int numHeaders = in.readInt();
|
||||
for (int i = 0; i < numHeaders; i++) {
|
||||
final String k = in.readString();
|
||||
final String v = in.readString();
|
||||
headers.add(Pair.create(k, v));
|
||||
}
|
||||
}
|
||||
|
||||
public HttpHeaders(Map<String, String> headersMap) {
|
||||
for (String key : headersMap.keySet()) {
|
||||
headers.add(new Pair<>(key, headersMap.get(key)));
|
||||
}
|
||||
}
|
||||
|
||||
public HttpHeaders() {
|
||||
}
|
||||
|
||||
public static final Creator<HttpHeaders> CREATOR = new Creator<HttpHeaders>() {
|
||||
@Override
|
||||
public HttpHeaders createFromParcel(final Parcel in) {
|
||||
return new HttpHeaders(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders[] newArray(final int size) {
|
||||
return new HttpHeaders[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull final Parcel dest, final int flags) {
|
||||
dest.writeInt(headers.size());
|
||||
for (final Pair<String, String> header : headers) {
|
||||
dest.writeString(header.first);
|
||||
dest.writeString(header.second);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Pair<String, String>> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public void addHeader(final String key, final String value) {
|
||||
headers.add(Pair.create(key, value));
|
||||
}
|
||||
|
||||
public String get(final String key) {
|
||||
for (final Pair<String, String> header : headers) {
|
||||
if (header.first.equalsIgnoreCase(key)) {
|
||||
return header.second;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map<String, String> toMap() {
|
||||
final Map<String, String> ret = new HashMap<>();
|
||||
for (final Pair<String, String> header : headers) {
|
||||
ret.put(header.first, header.second);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package nodomain.freeyourgadget.internethelper.aidl.http;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class HttpRequest implements Parcelable {
|
||||
public enum Method {
|
||||
GET,
|
||||
POST,
|
||||
HEAD,
|
||||
PUT,
|
||||
PATCH,
|
||||
DELETE,
|
||||
OPTIONS,
|
||||
}
|
||||
|
||||
private final String url;
|
||||
private final Method method;
|
||||
private final String body;
|
||||
private final String bodyContentType;
|
||||
private final boolean allowInsecure;
|
||||
private final HttpHeaders headers;
|
||||
|
||||
protected HttpRequest(final Parcel in) {
|
||||
url = in.readString();
|
||||
method = Method.values()[in.readInt()];
|
||||
body = in.readString();
|
||||
bodyContentType = in.readString();
|
||||
allowInsecure = in.readByte() != 0; // readBoolean() requires API level 29
|
||||
headers = in.readParcelable(HttpRequest.class.getClassLoader());
|
||||
}
|
||||
|
||||
public HttpRequest(String url, Method method, String body, String bodyContentType, boolean allowInsecure, HttpHeaders headers) {
|
||||
this.url = url;
|
||||
this.method = method;
|
||||
this.body = body;
|
||||
this.bodyContentType = bodyContentType;
|
||||
this.allowInsecure = allowInsecure;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public static final Creator<HttpRequest> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public HttpRequest createFromParcel(final Parcel in) {
|
||||
return new HttpRequest(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpRequest[] newArray(final int size) {
|
||||
return new HttpRequest[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull final Parcel dest, final int flags) {
|
||||
dest.writeString(url);
|
||||
dest.writeInt(method.ordinal());
|
||||
dest.writeString(body);
|
||||
dest.writeString(bodyContentType);
|
||||
dest.writeByte((byte) (allowInsecure ? 1 : 0)); // writeBoolean() requires API level 29
|
||||
dest.writeParcelable(headers, 0);
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public String getBodyContentType() {
|
||||
return bodyContentType;
|
||||
}
|
||||
|
||||
public boolean getAllowInsecure() {
|
||||
return allowInsecure;
|
||||
}
|
||||
|
||||
public HttpHeaders getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package nodomain.freeyourgadget.internethelper.aidl.http
|
||||
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class HttpRequest(
|
||||
val url: String,
|
||||
val method: Method,
|
||||
val headers: Map<String, String> = emptyMap(),
|
||||
val body: ParcelFileDescriptor? = null,
|
||||
val allowInsecure: Boolean = false,
|
||||
) : Parcelable {
|
||||
@Suppress("unused") // they must be kept so they can be sent
|
||||
enum class Method {
|
||||
GET,
|
||||
POST,
|
||||
HEAD,
|
||||
PUT,
|
||||
PATCH,
|
||||
DELETE,
|
||||
OPTIONS,
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package nodomain.freeyourgadget.internethelper.aidl.http;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class HttpResponse implements Parcelable {
|
||||
private final int status;
|
||||
private final HttpHeaders headers;
|
||||
private final ParcelFileDescriptor body;
|
||||
|
||||
protected HttpResponse(final Parcel in) {
|
||||
status = in.readInt();
|
||||
headers = in.readParcelable(HttpResponse.class.getClassLoader());
|
||||
body = in.readParcelable(HttpResponse.class.getClassLoader());
|
||||
}
|
||||
|
||||
public HttpResponse(final int status, final HttpHeaders headers, final ParcelFileDescriptor body) {
|
||||
this.status = status;
|
||||
this.headers = headers;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public static final Creator<HttpResponse> CREATOR = new Creator<HttpResponse>() {
|
||||
@Override
|
||||
public HttpResponse createFromParcel(final Parcel in) {
|
||||
return new HttpResponse(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpResponse[] newArray(final int size) {
|
||||
return new HttpResponse[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull final Parcel dest, final int flags) {
|
||||
dest.writeInt(status);
|
||||
dest.writeParcelable(headers, 0);
|
||||
dest.writeParcelable(body, 0);
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public HttpHeaders getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public ParcelFileDescriptor getBody() {
|
||||
return body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package nodomain.freeyourgadget.internethelper.aidl.http
|
||||
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class HttpResponse(
|
||||
val status: Int,
|
||||
val headers: Map<String, String> = emptyMap(),
|
||||
val body: ParcelFileDescriptor? = null,
|
||||
) : Parcelable
|
||||
Reference in New Issue
Block a user