Fix video deletion
parent
39f2af8dc3
commit
c7d2d78619
|
@ -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>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
|
@ -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())
|
||||||
|
|
|
@ -48,15 +48,20 @@ 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 {
|
||||||
|
when (request.method) {
|
||||||
|
"GET" -> {
|
||||||
routerGet(path)
|
routerGet(path)
|
||||||
} else if (request.method == "OPTIONS") {
|
}
|
||||||
|
"OPTIONS" -> {
|
||||||
WebResourceResponse("text/plain", "UTF-8", ByteArrayInputStream("".toByteArray()))
|
WebResourceResponse("text/plain", "UTF-8", ByteArrayInputStream("".toByteArray()))
|
||||||
} else {
|
}
|
||||||
|
else -> {
|
||||||
throw Exception("Method Not Allowed")
|
throw Exception("Method Not Allowed")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "handleRequest: ", e)
|
Log.e(TAG, "handleRequest: ", e)
|
||||||
makeErrorResponse()
|
makeErrorResponse()
|
||||||
|
@ -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)) {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 imageList = SystemImage.getByIds(mCtx, arrayListOf(id))
|
||||||
|
if (imageList.isEmpty()) {
|
||||||
|
throw Exception("File not found in any collection")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add EXIF to json object
|
||||||
|
val image = imageList[0];
|
||||||
|
val dayId = cursor.getLong(0)
|
||||||
val dateTaken = cursor.getLong(1)
|
val dateTaken = cursor.getLong(1)
|
||||||
val dayId = cursor.getLong(2)
|
|
||||||
|
|
||||||
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
|
|
||||||
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")
|
||||||
|
|
Loading…
Reference in New Issue