diff --git a/app/src/main/java/gallery/memories/MainActivity.java b/app/src/main/java/gallery/memories/MainActivity.java index ae738b53..b8bc3010 100644 --- a/app/src/main/java/gallery/memories/MainActivity.java +++ b/app/src/main/java/gallery/memories/MainActivity.java @@ -1,12 +1,6 @@ package gallery.memories; -import android.content.ContentUris; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.net.Uri; import android.os.Bundle; -import android.provider.MediaStore; -import android.util.Log; import android.webkit.JavascriptInterface; import android.webkit.WebResourceRequest; import android.webkit.WebSettings; @@ -15,22 +9,18 @@ import android.webkit.WebViewClient; import androidx.appcompat.app.AppCompatActivity; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Base64; - import gallery.memories.databinding.ActivityMainBinding; +import gallery.memories.service.ImageService; +import gallery.memories.service.JsService; +import gallery.memories.service.TimelineQuery; public class MainActivity extends AppCompatActivity { - - private ActivityMainBinding binding; - public static final String TAG = "memories-native"; + protected ActivityMainBinding binding; + + protected JsService mJsService; + protected ImageService mImageService; + protected TimelineQuery mQuery; @Override protected void onCreate(Bundle savedInstanceState) { @@ -39,16 +29,18 @@ public class MainActivity extends AppCompatActivity { binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); + mJsService = new JsService(this, binding.webview); + mImageService = new ImageService(this); + mQuery = new TimelineQuery(this); + initWebview(); } protected void initWebview() { binding.webview.setWebViewClient(new WebViewClient() { public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - // do your handling codes here, which url is the requested url - // probably you need to open that url rather than redirect: view.loadUrl(request.getUrl().toString()); - return false; // then it is not handled by default action + return false; } }); @@ -71,132 +63,11 @@ public class MainActivity extends AppCompatActivity { @JavascriptInterface public void getLocalByDayId(final String call, final long dayId) { - Log.d(TAG, "getLocalByDayId: " + dayId); - - new Thread(() -> { - Uri collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - - String[] projection = new String[] { - MediaStore.Images.Media._ID, - MediaStore.Images.Media.DISPLAY_NAME, - MediaStore.Images.Media.MIME_TYPE, - MediaStore.Images.Media.DATE_TAKEN, - MediaStore.Images.Media.HEIGHT, - MediaStore.Images.Media.WIDTH, - MediaStore.Images.Media.SIZE, - }; - String selection = MediaStore.Images.Media.DATE_TAKEN + " >= ? AND " - + MediaStore.Images.Media.DATE_TAKEN + " <= ?"; - String[] selectionArgs = new String[] { - Long.toString(dayId * 86400000L), - Long.toString(((dayId+1) * 86400000L)), - }; - - String sortOrder = MediaStore.Images.Media.DISPLAY_NAME + " ASC"; - - ArrayList files = new ArrayList<>(); - - try (Cursor cursor = getContentResolver().query( - collection, - projection, - selection, - selectionArgs, - sortOrder - )) { - int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID); - int nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME); - int mimeColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.MIME_TYPE); - int dateColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN); - int heightColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.HEIGHT); - int widthColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.WIDTH); - int sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE); - - while (cursor.moveToNext()) { - long id = cursor.getLong(idColumn); - String name = cursor.getString(nameColumn); - String mime = cursor.getString(mimeColumn); - long dateTaken = cursor.getLong(dateColumn); - long height = cursor.getLong(heightColumn); - long width = cursor.getLong(widthColumn); - long size = cursor.getLong(sizeColumn); - - Uri contentUri = ContentUris.withAppendedId( - MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id); - - try { - JSONObject file = new JSONObject() - .put("fileid", id) - .put("basename", name) - .put("mimetype", mime) - .put("dayid", (dateTaken / 86400000)) - .put("h", height) - .put("w", width) - .put("size", size); - files.add(file); - } catch (JSONException e) { - Log.e(TAG, "JSON error"); - } - } - - this.jsResolve(call, new JSONArray(files).toString()); - } - }).start(); + mJsService.runAsync(call, () -> mQuery.getByDayId(dayId).toString().getBytes()); } @JavascriptInterface - public void getJpeg(String call, String uri) { - Log.d(TAG, "getPreviewById: " + uri); - - // URI looks like nativex:/// - String[] parts = uri.split("/"); - if (parts.length != 4) { - this.jsReject(call, "Invalid URI"); - return; - } - - final String type = parts[2]; - final long id = Long.parseLong(parts[3]); - - new Thread(() -> { - Bitmap bitmap = null; - - if (type.equals("preview")) { - bitmap = MediaStore.Images.Thumbnails.getThumbnail( - getContentResolver(), id, MediaStore.Images.Thumbnails.MINI_KIND, null); - } else if (type.equals("full")) { - try { - bitmap = MediaStore.Images.Media.getBitmap( - getContentResolver(), ContentUris.withAppendedId( - MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)); - } catch (IOException e) { - e.printStackTrace(); - } - } else { - this.jsReject(call, "Invalid type"); - } - - if (bitmap == null) { - this.jsReject(call, "Thumbnail not found"); - return; - } - - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream); - jsResolve(call, stream.toByteArray()); - }).start(); - } - - protected void jsResolve(String call, byte[] ret) { - final String b64 = Base64.getEncoder().encodeToString(ret); - runOnUiThread(() -> binding.webview.loadUrl("javascript:(function() { window.nativexr('" + call + "', '" + b64 + "'); })();void(0);")); - } - - protected void jsResolve(String call, String ret) { - jsResolve(call, ret.getBytes()); - } - - protected void jsReject(String call, String ret) { - final String b64 = Base64.getEncoder().encodeToString(ret.getBytes()); - runOnUiThread(() -> binding.webview.loadUrl("javascript:(function() { window.nativexr('" + call + "', undefined, '" + b64 + "'); })();void(0);")); + public void getJpeg(final String call, final String uri) { + mJsService.runAsync(call, () -> mImageService.getFromURI(uri)); } } \ No newline at end of file diff --git a/app/src/main/java/gallery/memories/service/ImageService.java b/app/src/main/java/gallery/memories/service/ImageService.java new file mode 100644 index 00000000..b2dcac6d --- /dev/null +++ b/app/src/main/java/gallery/memories/service/ImageService.java @@ -0,0 +1,53 @@ +package gallery.memories.service; + +import android.content.ContentUris; +import android.content.Context; +import android.graphics.Bitmap; +import android.provider.MediaStore; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class ImageService { + Context mCtx; + + public ImageService(Context context) { + 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"); + } + + if (bitmap == null) { + throw new Exception("Thumbnail 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 new file mode 100644 index 00000000..7af7db54 --- /dev/null +++ b/app/src/main/java/gallery/memories/service/JsService.java @@ -0,0 +1,42 @@ +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)); + } +} diff --git a/app/src/main/java/gallery/memories/service/TimelineQuery.java b/app/src/main/java/gallery/memories/service/TimelineQuery.java new file mode 100644 index 00000000..94d942e3 --- /dev/null +++ b/app/src/main/java/gallery/memories/service/TimelineQuery.java @@ -0,0 +1,98 @@ +package gallery.memories.service; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.MediaStore; +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.TimeZone; + +public class TimelineQuery { + final static String TAG = "QueryService"; + Context mCtx; + + public TimelineQuery(Context context) { + mCtx = context; + } + + public JSONArray getByDayId(final long dayId) { + Uri collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + + // Offset of current timezone from UTC + long utcOffset = TimeZone.getDefault().getOffset(System.currentTimeMillis()); + + // Same fields as server response + String[] projection = new String[] { + MediaStore.Images.Media._ID, + MediaStore.Images.Media.DISPLAY_NAME, + MediaStore.Images.Media.MIME_TYPE, + MediaStore.Images.Media.DATE_TAKEN, + MediaStore.Images.Media.HEIGHT, + MediaStore.Images.Media.WIDTH, + MediaStore.Images.Media.SIZE, + }; + + // Filter for given day + String selection = MediaStore.Images.Media.DATE_TAKEN + " >= ? AND " + + MediaStore.Images.Media.DATE_TAKEN + " <= ?"; + String[] selectionArgs = new String[] { + Long.toString(dayId * 86400000L - utcOffset), + Long.toString(((dayId+1) * 86400000L - utcOffset)), + }; + + // Sort by name? TODO: fix this + String sortOrder = MediaStore.Images.Media.DISPLAY_NAME + " ASC"; + + // Make list of files + ArrayList files = new ArrayList<>(); + + try (Cursor cursor = mCtx.getContentResolver().query( + collection, + projection, + selection, + selectionArgs, + sortOrder + )) { + int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID); + int nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME); + int mimeColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.MIME_TYPE); + int dateColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN); + int heightColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.HEIGHT); + int widthColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.WIDTH); + int sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE); + + while (cursor.moveToNext()) { + long id = cursor.getLong(idColumn); + String name = cursor.getString(nameColumn); + String mime = cursor.getString(mimeColumn); + long dateTaken = cursor.getLong(dateColumn); + long height = cursor.getLong(heightColumn); + long width = cursor.getLong(widthColumn); + long size = cursor.getLong(sizeColumn); + + try { + JSONObject file = new JSONObject() + .put("fileid", id) + .put("basename", name) + .put("mimetype", mime) + .put("dayid", (dateTaken / 86400000)) + .put("h", height) + .put("w", width) + .put("size", size); + files.add(file); + } catch (JSONException e) { + Log.e(TAG, "JSON error"); + } + } + } + + // Return JSON string of files + return new JSONArray(files); + } +}