Fix video deletion

pull/653/merge
Varun Patil 2023-05-13 17:16:39 -07:00
parent 39f2af8dc3
commit c7d2d78619
6 changed files with 244 additions and 69 deletions

View File

@ -0,0 +1,123 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@ -26,7 +26,7 @@ class MainActivity : AppCompatActivity() {
} }
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
protected fun initializeWebView() { private fun initializeWebView() {
binding.webview.webViewClient = object : WebViewClient() { binding.webview.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
view.loadUrl(request.url.toString()) view.loadUrl(request.url.toString())

View File

@ -48,14 +48,19 @@ class NativeX(private val mActivity: AppCompatActivity) {
} }
fun handleRequest(request: WebResourceRequest): WebResourceResponse { fun handleRequest(request: WebResourceRequest): WebResourceResponse {
val path = request.url.path val path = request.url.path ?: return makeErrorResponse()
val response: WebResourceResponse = try {
if (request.method == "GET") { val response = try {
routerGet(path) when (request.method) {
} else if (request.method == "OPTIONS") { "GET" -> {
WebResourceResponse("text/plain", "UTF-8", ByteArrayInputStream("".toByteArray())) routerGet(path)
} else { }
throw Exception("Method Not Allowed") "OPTIONS" -> {
WebResourceResponse("text/plain", "UTF-8", ByteArrayInputStream("".toByteArray()))
}
else -> {
throw Exception("Method Not Allowed")
}
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "handleRequest: ", e) Log.e(TAG, "handleRequest: ", e)
@ -89,12 +94,13 @@ class NativeX(private val mActivity: AppCompatActivity) {
@JavascriptInterface @JavascriptInterface
fun downloadFromUrl(url: String?, filename: String?) { fun downloadFromUrl(url: String?, filename: String?) {
mDlService.queue(url!!, filename!!) if (url == null || filename == null) return;
mDlService.queue(url, filename)
} }
@Throws(Exception::class) @Throws(Exception::class)
private fun routerGet(path: String?): WebResourceResponse { private fun routerGet(path: String): WebResourceResponse {
val parts = path!!.split("/").toTypedArray() val parts = path.split("/").toTypedArray()
if (path.matches(API.IMAGE_PREVIEW)) { if (path.matches(API.IMAGE_PREVIEW)) {
return makeResponse(mImageService.getPreview(parts[3].toLong()), "image/jpeg") return makeResponse(mImageService.getPreview(parts[3].toLong()), "image/jpeg")
} else if (path.matches(API.IMAGE_FULL)) { } else if (path.matches(API.IMAGE_FULL)) {

View File

@ -0,0 +1,76 @@
package gallery.memories.mapper
import android.content.ContentUris
import android.content.Context
import android.net.Uri
import android.provider.MediaStore
class SystemImage {
var fileId = 0L;
var baseName = ""
var mimeType = ""
var dateTaken = 0L
var height = 0L
var width = 0L
var size = 0L
var dataPath = ""
var isVideo = false
private var mCollection: Uri = IMAGE_URI
val uri: Uri
get() {
return ContentUris.withAppendedId(mCollection, fileId)
}
companion object {
val IMAGE_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val VIDEO_URI = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
fun getByIds(ctx: Context, ids: List<Long>): List<SystemImage> {
val images = getByIdsAndCollection(ctx, IMAGE_URI, ids)
if (images.size == ids.size) return images
return images + getByIdsAndCollection(ctx, VIDEO_URI, ids)
}
fun getByIdsAndCollection(ctx: Context, collection: Uri, ids: List<Long>): List<SystemImage> {
val selection = MediaStore.Images.Media._ID + " IN (" + ids.joinToString(",") + ")"
val list = mutableListOf<SystemImage>()
ctx.contentResolver.query(
collection,
arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.MIME_TYPE,
MediaStore.Images.Media.HEIGHT,
MediaStore.Images.Media.WIDTH,
MediaStore.Images.Media.SIZE,
MediaStore.Images.Media.DATE_TAKEN,
MediaStore.Images.Media.DATA
),
selection,
null,
null
).use { cursor ->
while (cursor!!.moveToNext()) {
val image = SystemImage()
image.fileId = cursor.getLong(0)
image.baseName = cursor.getString(1)
image.mimeType = cursor.getString(2)
image.height = cursor.getLong(3)
image.width = cursor.getLong(4)
image.size = cursor.getLong(5)
image.dateTaken = cursor.getLong(6)
image.dataPath = cursor.getString(7)
image.isVideo = collection == VIDEO_URI
image.mCollection = collection
list.add(image)
}
}
return list
}
}
}

View File

@ -2,11 +2,9 @@ package gallery.memories.service
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.ContentUris
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.icu.text.SimpleDateFormat import android.icu.text.SimpleDateFormat
import android.icu.util.TimeZone import android.icu.util.TimeZone
import android.net.Uri
import android.os.Build import android.os.Build
import android.provider.MediaStore import android.provider.MediaStore
import android.text.TextUtils import android.text.TextUtils
@ -18,6 +16,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.collection.ArraySet import androidx.collection.ArraySet
import androidx.exifinterface.media.ExifInterface import androidx.exifinterface.media.ExifInterface
import gallery.memories.mapper.SystemImage
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
@ -165,68 +164,36 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
@Throws(Exception::class) @Throws(Exception::class)
fun getImageInfo(id: Long): JSONObject { fun getImageInfo(id: Long): JSONObject {
val sql = "SELECT local_id, date_taken, dayid FROM images WHERE local_id = ?" val sql = "SELECT dayid, date_taken FROM images WHERE local_id = ?"
mDb.rawQuery(sql, arrayOf(id.toString())).use { cursor -> mDb.rawQuery(sql, arrayOf(id.toString())).use { cursor ->
if (!cursor.moveToNext()) { if (!cursor.moveToNext()) {
throw Exception("Image not found") throw Exception("Image not found")
} }
val localId = cursor.getLong(0) // Get image from system table
val dateTaken = cursor.getLong(1) val imageList = SystemImage.getByIds(mCtx, arrayListOf(id))
val dayId = cursor.getLong(2) if (imageList.isEmpty()) {
throw Exception("File not found in any collection")
return getImageInfoForCollection(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
localId, dateTaken, dayId)
?: return getImageInfoForCollection(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
localId, dateTaken, dayId)
?: throw Exception("File not found in any collection")
}
}
private fun getImageInfoForCollection(
collection: Uri,
localId: Long,
dateTaken: Long,
dayId: Long
): JSONObject? {
val selection = MediaStore.Images.Media._ID + " = " + localId
mCtx.contentResolver.query(
collection,
arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.MIME_TYPE,
MediaStore.Images.Media.HEIGHT,
MediaStore.Images.Media.WIDTH,
MediaStore.Images.Media.SIZE,
MediaStore.Images.Media.DATA
),
selection,
null,
null
).use { cursor ->
if (!cursor!!.moveToNext()) {
throw Exception("Image not found")
} }
// Create basic info // Add EXIF to json object
val image = imageList[0];
val dayId = cursor.getLong(0)
val dateTaken = cursor.getLong(1)
val obj = JSONObject() val obj = JSONObject()
.put(Fields.Photo.FILEID, cursor.getLong(0)) .put(Fields.Photo.FILEID, image.fileId)
.put(Fields.Photo.BASENAME, cursor.getString(1)) .put(Fields.Photo.BASENAME, image.baseName)
.put(Fields.Photo.MIMETYPE, cursor.getString(2)) .put(Fields.Photo.MIMETYPE, image.mimeType)
.put(Fields.Photo.DAYID, dayId) .put(Fields.Photo.DAYID, dayId)
.put(Fields.Photo.DATETAKEN, dateTaken) .put(Fields.Photo.DATETAKEN, dateTaken)
.put(Fields.Photo.HEIGHT, cursor.getLong(3)) .put(Fields.Photo.HEIGHT, image.height)
.put(Fields.Photo.WIDTH, cursor.getLong(4)) .put(Fields.Photo.WIDTH, image.width)
.put(Fields.Photo.SIZE, cursor.getLong(5)) .put(Fields.Photo.SIZE, image.size)
.put(Fields.Photo.PERMISSIONS, Fields.Perm.DELETE) .put(Fields.Photo.PERMISSIONS, Fields.Perm.DELETE)
val uri = cursor.getString(6)
// Get EXIF data
try { try {
val exif = ExifInterface(uri) val exif = ExifInterface(image.dataPath)
obj.put(Fields.Photo.EXIF, JSONObject() obj.put(Fields.Photo.EXIF, JSONObject()
.put("Aperture", exif.getAttribute(ExifInterface.TAG_APERTURE_VALUE)) .put("Aperture", exif.getAttribute(ExifInterface.TAG_APERTURE_VALUE))
.put("FocalLength", exif.getAttribute(ExifInterface.TAG_FOCAL_LENGTH)) .put("FocalLength", exif.getAttribute(ExifInterface.TAG_FOCAL_LENGTH))
@ -245,7 +212,7 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
.put("Description", exif.getAttribute(ExifInterface.TAG_IMAGE_DESCRIPTION)) .put("Description", exif.getAttribute(ExifInterface.TAG_IMAGE_DESCRIPTION))
) )
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, "Error reading EXIF data for $uri") Log.e(TAG, "Error reading EXIF data for $id")
} }
return obj return obj
@ -263,9 +230,7 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
return try { return try {
// List of URIs // List of URIs
val uris = ids.map { val uris = SystemImage.getByIds(mCtx, ids).map { it.uri }
ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, it)
}
// Delete file with media store // Delete file with media store
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
@ -301,7 +266,7 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
} }
} }
protected fun fullSyncDb() { private fun fullSyncDb() {
// Flag all images for removal // Flag all images for removal
mDb.execSQL("UPDATE images SET flag = 1") mDb.execSQL("UPDATE images SET flag = 1")
mCtx.contentResolver.query( mCtx.contentResolver.query(
@ -381,7 +346,7 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
// Get EXIF date using ExifInterface if image // Get EXIF date using ExifInterface if image
if (!isVideo) { if (!isVideo) {
try { try {
val exif = ExifInterface(uri!!) val exif = ExifInterface(uri)
val exifDate = exif.getAttribute(ExifInterface.TAG_DATETIME) val exifDate = exif.getAttribute(ExifInterface.TAG_DATETIME)
?: throw IOException() ?: throw IOException()
val sdf = SimpleDateFormat("yyyy:MM:dd HH:mm:ss") val sdf = SimpleDateFormat("yyyy:MM:dd HH:mm:ss")