diff --git a/app/src/main/java/gallery/memories/MainActivity.java b/app/src/main/java/gallery/memories/MainActivity.java index fab4de7d..c71e4433 100644 --- a/app/src/main/java/gallery/memories/MainActivity.java +++ b/app/src/main/java/gallery/memories/MainActivity.java @@ -3,6 +3,7 @@ package gallery.memories; import android.annotation.SuppressLint; import android.os.Bundle; import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; @@ -34,6 +35,14 @@ public class MainActivity extends AppCompatActivity { view.loadUrl(request.getUrl().toString()); return false; } + + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { + if (request.getUrl().getHost().equals("127.0.0.1")) { + return mNativeX.handleRequest(request.getUrl().getPath()); + } + return null; + } }); WebSettings webSettings = binding.webview.getSettings(); diff --git a/app/src/main/java/gallery/memories/NativeX.java b/app/src/main/java/gallery/memories/NativeX.java index 1aa5e436..58ff41cb 100644 --- a/app/src/main/java/gallery/memories/NativeX.java +++ b/app/src/main/java/gallery/memories/NativeX.java @@ -5,29 +5,80 @@ import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; import android.app.Activity; import android.graphics.Color; import android.os.Build; +import android.util.Log; import android.view.View; import android.view.Window; import android.webkit.JavascriptInterface; +import android.webkit.WebResourceResponse; import android.webkit.WebView; +import androidx.collection.ArrayMap; + +import java.io.ByteArrayInputStream; +import java.util.Map; + import gallery.memories.service.ImageService; -import gallery.memories.service.JsService; import gallery.memories.service.TimelineQuery; public class NativeX { + public static final String TAG = "NativeX"; Activity mActivity; - protected JsService mJsService; protected ImageService mImageService; protected TimelineQuery mQuery; public NativeX(Activity activity, WebView webView) { mActivity = activity; - mJsService = new JsService(activity, webView); mImageService = new ImageService(activity); mQuery = new TimelineQuery(activity); } + public WebResourceResponse handleRequest(final String path) { + byte[] bytes = null; + String mimeType = "application/json"; + + try { + // Match the path using regex + String[] parts = path.split("/"); + + if (path.matches("^/image/preview/\\d+$")) { + // Preview Image + bytes = mImageService.getPreview(Long.parseLong(parts[3])); + mimeType = "image/jpeg"; + } else if (path.matches("^/image/full/\\d+$")) { + // Full sized image + bytes = mImageService.getFull(Long.parseLong(parts[3])); + mimeType = "image/jpeg"; + } else if (path.matches("^/api/days$")) { + // Days list + bytes = mQuery.getDays().toString().getBytes(); + } else if (path.matches("/api/days/\\d+$")) { + // Single day photos + bytes = mQuery.getByDayId(Long.parseLong(parts[3])).toString().getBytes(); + } else { + Log.e(TAG, "handleRequest: Unknown path: " + path); + } + } catch (Exception e) { + Log.e(TAG, "handleRequest: ", e); + } + + // Construct the response + WebResourceResponse response; + if (bytes != null) { + response = new WebResourceResponse(mimeType, "UTF-8", new ByteArrayInputStream(bytes)); + } else { + response = new WebResourceResponse(mimeType, "UTF-8", new ByteArrayInputStream("{}".getBytes())); + response.setStatusCodeAndReasonPhrase(500, "Internal Server Error"); + } + + // Allow CORS from all origins + Map headers = new ArrayMap<>(); + headers.put("Access-Control-Allow-Origin", "*"); + response.setResponseHeaders(headers); + + return response; + } + @JavascriptInterface public boolean isNative() { return true; @@ -49,19 +100,4 @@ public class NativeX { window.getDecorView().setSystemUiVisibility(isDark ? 0 : View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } } - - @JavascriptInterface - public void getLocalDays(final String call, final long _ignore) { - mJsService.runAsync(call, () -> mQuery.getDays().toString().getBytes()); - } - - @JavascriptInterface - public void getLocalByDayId(final String call, final long dayId) { - mJsService.runAsync(call, () -> mQuery.getByDayId(dayId).toString().getBytes()); - } - - @JavascriptInterface - public void getJpeg(final String call, final String uri) { - mJsService.runAsync(call, () -> mImageService.getFromURI(uri)); - } } diff --git a/app/src/main/java/gallery/memories/service/ImageService.java b/app/src/main/java/gallery/memories/service/ImageService.java index b2dcac6d..e798518c 100644 --- a/app/src/main/java/gallery/memories/service/ImageService.java +++ b/app/src/main/java/gallery/memories/service/ImageService.java @@ -15,32 +15,9 @@ public class ImageService { mCtx = context; } - public byte[] getFromURI(String uri) throws Exception { - // URI looks like nativex:/// - String[] parts = uri.split("/"); - if (parts.length != 4) { - throw new Exception("Invalid URI path"); - } - - final String type = parts[2]; - final long id = Long.parseLong(parts[3]); - - Bitmap bitmap = null; - - if (type.equals("preview")) { - bitmap = MediaStore.Images.Thumbnails.getThumbnail( - mCtx.getContentResolver(), id, MediaStore.Images.Thumbnails.MINI_KIND, null); - } else if (type.equals("full")) { - try { - bitmap = MediaStore.Images.Media.getBitmap( - mCtx.getContentResolver(), ContentUris.withAppendedId( - MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)); - } catch (IOException e) { - e.printStackTrace(); - } - } else { - throw new Exception("Invalid request type"); - } + public byte[] getPreview(final long id) throws Exception { + Bitmap bitmap = MediaStore.Images.Thumbnails.getThumbnail( + mCtx.getContentResolver(), id, MediaStore.Images.Thumbnails.MINI_KIND, null); if (bitmap == null) { throw new Exception("Thumbnail not found"); @@ -50,4 +27,18 @@ public class ImageService { bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream); return stream.toByteArray(); } + + public byte[] getFull(final long id) throws Exception { + Bitmap bitmap = MediaStore.Images.Media.getBitmap( + mCtx.getContentResolver(), ContentUris.withAppendedId( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)); + + if (bitmap == null) { + throw new Exception("Image not found"); + } + + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream); + return stream.toByteArray(); + } } diff --git a/app/src/main/java/gallery/memories/service/JsService.java b/app/src/main/java/gallery/memories/service/JsService.java deleted file mode 100644 index 7af7db54..00000000 --- a/app/src/main/java/gallery/memories/service/JsService.java +++ /dev/null @@ -1,42 +0,0 @@ -package gallery.memories.service; - -import android.app.Activity; -import android.webkit.WebView; - -import java.util.Base64; - -public class JsService { - protected Activity mActivity; - protected WebView mWebView; - - public JsService(Activity activity, WebView webView) { - mActivity = activity; - mWebView = webView; - } - - public interface AsyncFunction { - byte[] run() throws Exception; - } - - public void runAsync(final String call, final AsyncFunction callable) { - new Thread(() -> { - try { - jsResolve(call, callable.run()); - } catch (Exception e) { - jsReject(call, e); - } - }).start(); - } - - protected void jsResolve(String call, byte[] ret) { - final String b64 = Base64.getEncoder().encodeToString(ret); - mActivity.runOnUiThread(() -> mWebView.evaluateJavascript("window.nativexr('" + call + "', '" + b64 + "');", null)); - } - - protected void jsReject(String call, Exception e) { - String message = e.getMessage(); - message = message == null ? "Unknown error occured" : message; - final String b64 = Base64.getEncoder().encodeToString(message.getBytes()); - mActivity.runOnUiThread(() -> mWebView.evaluateJavascript("window.nativexr('" + call + "', undefined, '" + b64 + "');", null)); - } -}