diff --git a/app/src/main/java/gallery/memories/mapper/SystemImage.kt b/app/src/main/java/gallery/memories/mapper/SystemImage.kt index 81e28096..78a9f703 100644 --- a/app/src/main/java/gallery/memories/mapper/SystemImage.kt +++ b/app/src/main/java/gallery/memories/mapper/SystemImage.kt @@ -13,49 +13,62 @@ class SystemImage { var height = 0L var width = 0L var size = 0L + var mtime = 0L var dataPath = "" - var isVideo = false - private var mCollection: Uri = IMAGE_URI + var isVideo = false + var videoDuration = 0L val uri: Uri get() { return ContentUris.withAppendedId(mCollection, fileId) } + private var mCollection: Uri = IMAGE_URI + 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): List { - 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): List { - val selection = MediaStore.Images.Media._ID + " IN (" + ids.joinToString(",") + ")" - + fun query( + ctx: Context, + collection: Uri, + selection: String?, + selectionArgs: Array?, + sortOrder: String? + ): List { val list = mutableListOf() + // Base fields common for videos and images + val projection = arrayListOf( + 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.DATE_MODIFIED, + MediaStore.Images.Media.DATA + ) + + // Add video-specific fields + if (collection == VIDEO_URI) { + projection.add(MediaStore.Video.Media.DURATION) + } + + // Query content resolver 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 - ), + projection.toTypedArray(), selection, - null, - null + selectionArgs, + sortOrder ).use { cursor -> while (cursor!!.moveToNext()) { val image = SystemImage() + + // Common fields image.fileId = cursor.getLong(0) image.baseName = cursor.getString(1) image.mimeType = cursor.getString(2) @@ -63,14 +76,29 @@ class SystemImage { 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.mtime = cursor.getLong(7) + image.dataPath = cursor.getString(8) image.mCollection = collection + + // Video specific fields + image.isVideo = collection == VIDEO_URI + if (image.isVideo) { + image.videoDuration = cursor.getLong(9) + } + + // Add to main list list.add(image) } } return list } + + fun getByIds(ctx: Context, ids: List): List { + val selection = MediaStore.Images.Media._ID + " IN (" + ids.joinToString(",") + ")" + val images = query(ctx, IMAGE_URI, selection, null, null) + if (images.size == ids.size) return images + return images + query(ctx, VIDEO_URI, selection, null, null) + } } } \ No newline at end of file diff --git a/app/src/main/java/gallery/memories/service/DbService.kt b/app/src/main/java/gallery/memories/service/DbService.kt index 321a16f5..cc8b8075 100644 --- a/app/src/main/java/gallery/memories/service/DbService.kt +++ b/app/src/main/java/gallery/memories/service/DbService.kt @@ -4,7 +4,7 @@ import android.content.Context import android.database.sqlite.SQLiteOpenHelper import android.database.sqlite.SQLiteDatabase -class DbService(context: Context) : SQLiteOpenHelper(context, "memories", null, 25) { +class DbService(context: Context) : SQLiteOpenHelper(context, "memories", null, 26) { override fun onCreate(db: SQLiteDatabase) { db.execSQL(""" CREATE TABLE images ( diff --git a/app/src/main/java/gallery/memories/service/Fields.kt b/app/src/main/java/gallery/memories/service/Fields.kt index 5f5b677f..834f8c51 100644 --- a/app/src/main/java/gallery/memories/service/Fields.kt +++ b/app/src/main/java/gallery/memories/service/Fields.kt @@ -1,6 +1,11 @@ package gallery.memories.service class Fields { + object Day { + const val DAYID = Photo.DAYID + const val COUNT = "count" + } + object Photo { const val FILEID = "fileid" const val BASENAME = "basename" diff --git a/app/src/main/java/gallery/memories/service/TimelineQuery.kt b/app/src/main/java/gallery/memories/service/TimelineQuery.kt index e9137f4d..fe6198b8 100644 --- a/app/src/main/java/gallery/memories/service/TimelineQuery.kt +++ b/app/src/main/java/gallery/memories/service/TimelineQuery.kt @@ -47,7 +47,7 @@ class TimelineQuery(private val mCtx: AppCompatActivity) { @Throws(JSONException::class) fun getByDayId(dayId: Long): JSONArray { // Get list of images from DB - val imageIds: MutableSet = ArraySet() + val imageIds: MutableSet = ArraySet() val datesTaken: MutableMap = HashMap() val sql = "SELECT local_id, date_taken FROM images WHERE dayid = ?" mDb.rawQuery(sql, arrayOf(dayId.toString())).use { cursor -> @@ -62,75 +62,26 @@ class TimelineQuery(private val mCtx: AppCompatActivity) { if (imageIds.size == 0) return JSONArray() // Filter for given day - val idColName = MediaStore.Images.Media._ID - val imageIdsSl = TextUtils.join(",", imageIds) - val selection = "$idColName IN ($imageIdsSl)" + val photos = JSONArray() + SystemImage.getByIds(mCtx, imageIds.toMutableList()).forEach { image -> + val obj = JSONObject() + .put(Fields.Photo.FILEID, image.fileId) + .put(Fields.Photo.BASENAME, image.baseName) + .put(Fields.Photo.MIMETYPE, image.mimeType) + .put(Fields.Photo.HEIGHT, image.height) + .put(Fields.Photo.WIDTH, image.width) + .put(Fields.Photo.SIZE, image.size) + .put(Fields.Photo.ETAG, image.mtime.toString()) + .put(Fields.Photo.DATETAKEN, datesTaken[image.fileId]) + .put(Fields.Photo.DAYID, dayId) - // Make list of files - val files = ArrayList() - mCtx.contentResolver.query( - MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - 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_MODIFIED - ), - selection, - null, - null - ).use { cursor -> - while (cursor?.moveToNext() == true) { - val fileId = cursor.getLong(0) - imageIds.remove(fileId) - files.add(JSONObject() - .put(Fields.Photo.FILEID, fileId) - .put(Fields.Photo.BASENAME, cursor.getString(1)) - .put(Fields.Photo.MIMETYPE, cursor.getString(2)) - .put(Fields.Photo.HEIGHT, cursor.getLong(3)) - .put(Fields.Photo.WIDTH, cursor.getLong(4)) - .put(Fields.Photo.SIZE, cursor.getLong(5)) - .put(Fields.Photo.ETAG, java.lang.Long.toString(cursor.getLong(6))) - .put(Fields.Photo.DATETAKEN, datesTaken[fileId]) - .put(Fields.Photo.DAYID, dayId)) - } - } - mCtx.contentResolver.query( - MediaStore.Video.Media.EXTERNAL_CONTENT_URI, - arrayOf( - MediaStore.Video.Media._ID, - MediaStore.Video.Media.DISPLAY_NAME, - MediaStore.Video.Media.MIME_TYPE, - MediaStore.Video.Media.HEIGHT, - MediaStore.Video.Media.WIDTH, - MediaStore.Video.Media.SIZE, - MediaStore.Video.Media.DATE_MODIFIED, - MediaStore.Video.Media.DURATION - ), - selection, - null, - null - ).use { cursor -> - while (cursor?.moveToNext() == true) { - // Remove from list of ids - val fileId = cursor.getLong(0) - imageIds.remove(fileId) - files.add(JSONObject() - .put(Fields.Photo.FILEID, fileId) - .put(Fields.Photo.BASENAME, cursor.getString(1)) - .put(Fields.Photo.MIMETYPE, cursor.getString(2)) - .put(Fields.Photo.HEIGHT, cursor.getLong(3)) - .put(Fields.Photo.WIDTH, cursor.getLong(4)) - .put(Fields.Photo.SIZE, cursor.getLong(5)) - .put(Fields.Photo.ETAG, java.lang.Long.toString(cursor.getLong(6))) - .put(Fields.Photo.DATETAKEN, datesTaken[fileId]) - .put(Fields.Photo.DAYID, dayId) - .put(Fields.Photo.ISVIDEO, 1) - .put(Fields.Photo.VIDEO_DURATION, cursor.getLong(7) / 1000)) + if (image.isVideo) { + obj.put(Fields.Photo.ISVIDEO, 1) + .put(Fields.Photo.VIDEO_DURATION, image.videoDuration / 1000) } + + photos.put(obj) + imageIds.remove(image.fileId) } // Remove files that were not found @@ -139,8 +90,7 @@ class TimelineQuery(private val mCtx: AppCompatActivity) { mDb.execSQL("DELETE FROM images WHERE local_id IN ($delIds)") } - // Return JSON string of files - return JSONArray(files) + return photos } @Throws(JSONException::class) @@ -151,11 +101,9 @@ class TimelineQuery(private val mCtx: AppCompatActivity) { ).use { cursor -> val days = JSONArray() while (cursor.moveToNext()) { - val id = cursor.getLong(0) - val count = cursor.getLong(1) days.put(JSONObject() - .put("dayid", id) - .put("count", count) + .put(Fields.Day.DAYID, cursor.getLong(0)) + .put(Fields.Day.COUNT, cursor.getLong(1)) ) } return days @@ -269,69 +217,22 @@ class TimelineQuery(private val mCtx: AppCompatActivity) { private fun fullSyncDb() { // Flag all images for removal mDb.execSQL("UPDATE images SET flag = 1") - mCtx.contentResolver.query( - MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - arrayOf( - MediaStore.Images.Media._ID, - MediaStore.Images.Media.DISPLAY_NAME, - MediaStore.Images.Media.DATE_TAKEN, - MediaStore.Images.Media.DATE_MODIFIED, - MediaStore.Images.Media.DATA - ), - null, - null, - null - ).use { cursor -> - while (cursor!!.moveToNext()) { - insertItemDb( - cursor.getLong(0), - cursor.getString(1), - cursor.getLong(2), - cursor.getLong(3), - cursor.getString(4), - false - ) - } - } - mCtx.contentResolver.query( - MediaStore.Video.Media.EXTERNAL_CONTENT_URI, - arrayOf( - MediaStore.Video.Media._ID, - MediaStore.Video.Media.DISPLAY_NAME, - MediaStore.Video.Media.DATE_TAKEN, - MediaStore.Video.Media.DATE_MODIFIED, - MediaStore.Video.Media.DATA - ), - null, - null, - null - ).use { cursor -> - while (cursor!!.moveToNext()) { - insertItemDb( - cursor.getLong(0), - cursor.getString(1), - cursor.getLong(2), - cursor.getLong(3), - cursor.getString(4), - true - ) - } - } + + // Iterate all images and videos from system store + val files = + SystemImage.query(mCtx, SystemImage.IMAGE_URI, null, null, null) + + SystemImage.query(mCtx, SystemImage.VIDEO_URI, null, null, null) + files.forEach { insertItemDb(it) } // Clean up stale files mDb.execSQL("DELETE FROM images WHERE flag = 1") } @SuppressLint("SimpleDateFormat") - private fun insertItemDb( - id: Long, - name: String, - dateTaken: Long, - mtime: Long, - uri: String, - isVideo: Boolean, - ) { - var dateTaken = dateTaken + private fun insertItemDb(image: SystemImage) { + var dateTaken = image.dateTaken + val id = image.fileId + val name = image.baseName // Check if file with local_id and mtime already exists mDb.rawQuery("SELECT id FROM images WHERE local_id = ?", arrayOf(id.toString())).use { c -> @@ -344,9 +245,9 @@ class TimelineQuery(private val mCtx: AppCompatActivity) { } // Get EXIF date using ExifInterface if image - if (!isVideo) { + if (!image.isVideo) { try { - val exif = ExifInterface(uri) + val exif = ExifInterface(image.dataPath) val exifDate = exif.getAttribute(ExifInterface.TAG_DATETIME) ?: throw IOException() val sdf = SimpleDateFormat("yyyy:MM:dd HH:mm:ss") @@ -361,7 +262,7 @@ class TimelineQuery(private val mCtx: AppCompatActivity) { } // No way to get the actual local date, so just assume current timezone - if (isVideo) { + else { // !isVideo dateTaken += TimeZone.getDefault().getOffset(dateTaken).toLong() } @@ -372,7 +273,9 @@ class TimelineQuery(private val mCtx: AppCompatActivity) { // Delete file with same local_id and insert new one mDb.beginTransaction() mDb.execSQL("DELETE FROM images WHERE local_id = ?", arrayOf(id)) - mDb.execSQL("INSERT OR IGNORE INTO images (local_id, mtime, basename, date_taken, dayid) VALUES (?, ?, ?, ?, ?)", arrayOf(id, mtime, name, dateTaken, dayId)) + mDb.execSQL("INSERT OR IGNORE INTO images (local_id, mtime, basename, date_taken, dayid) VALUES (?, ?, ?, ?, ?)", arrayOf( + id, image.mtime, name, dateTaken, dayId + )) mDb.setTransactionSuccessful() mDb.endTransaction() Log.v(TAG, "Inserted file to local DB: $id / $name / $dayId")