pull/653/merge
Varun Patil 2023-05-07 13:02:04 -07:00
parent c9a9e4379b
commit 37415f7dd5
4 changed files with 209 additions and 145 deletions

View File

@ -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<JSONObject> 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://<type>/<id>
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));
}
}

View File

@ -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://<type>/<id>
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();
}
}

View File

@ -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));
}
}

View File

@ -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<JSONObject> 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);
}
}