1 package net.osdn.gokigen.pkremote.playback
3 import android.app.Activity
4 import android.app.AlertDialog
5 import android.app.ProgressDialog
6 import android.content.ContentValues
7 import android.content.Intent
8 import android.database.DatabaseUtils
10 import android.os.Build
11 import android.os.Environment
12 import android.provider.MediaStore
13 import android.util.Log
14 import android.view.View
15 import androidx.preference.PreferenceManager
16 import com.google.android.material.snackbar.Snackbar
17 import net.osdn.gokigen.pkremote.R
18 import net.osdn.gokigen.pkremote.camera.interfaces.playback.ICameraContent
19 import net.osdn.gokigen.pkremote.camera.interfaces.playback.IDownloadContentCallback
20 import net.osdn.gokigen.pkremote.camera.interfaces.playback.IPlaybackControl
21 import net.osdn.gokigen.pkremote.camera.interfaces.playback.IProgressEvent
22 import net.osdn.gokigen.pkremote.preference.IPreferencePropertyAccessor
24 import java.io.OutputStream
25 import java.text.SimpleDateFormat
28 class MyContentDownloader(private val activity : Activity, private val playbackControl : IPlaybackControl, private val receiver : IContentDownloadNotify?) : IDownloadContentCallback
30 private lateinit var downloadDialog : ProgressDialog //= ProgressDialog(activity)
31 private val dumpLog = false
32 private var outputStream: OutputStream? = null
33 private var targetFileName = ""
34 private var filepath = ""
35 private var mimeType = "image/jpeg"
36 private var isDownloading = false
37 private var imageUri : Uri? = null
39 private fun getExternalOutputDirectory(): File
41 val directoryPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).path + "/" + activity.getString(R.string.app_name2) + "/"
42 val target = File(directoryPath)
51 Log.v(TAG, " ----- RECORD Directory PATH : $directoryPath -----")
55 private fun isExternalStorageWritable(): Boolean
57 return (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED)
64 fun startDownload(fileInfo: ICameraContent?, appendTitle: String, replaceJpegSuffix: String?, requestSmallSize: Boolean)
68 Log.v(TAG, "startDownload() ICameraFileInfo is NULL...")
72 // Download the image.
73 var isSmallSize = requestSmallSize
77 var contentFileName = fileInfo.contentName.toUpperCase(Locale.US)
78 if (replaceJpegSuffix != null)
80 contentFileName = contentFileName.replace(JPEG_SUFFIX, replaceJpegSuffix)
81 targetFileName = contentFileName
85 targetFileName = fileInfo.originalName.toUpperCase(Locale.US)
87 Log.v(TAG, "startDownload() $targetFileName")
89 contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_1) -> {
90 mimeType = "image/x-adobe-dng"
93 contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_2) -> {
94 mimeType = "image/x-olympus-orf"
97 contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_3) -> {
98 mimeType = "image/x-pentax-pef"
101 contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_4) -> {
102 mimeType = "image/x-panasonic-rw2"
105 contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_A) -> {
107 mimeType = "image/x-panasonic-raw"
110 contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_5) -> {
111 mimeType = "image/x-sony-arw"
114 contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_6) -> {
115 mimeType = "image/x-canon-crw"
118 contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_7) -> {
119 mimeType = "image/x-canon-cr2"
122 contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_8) -> {
123 mimeType = "image/x-canon-cr3"
126 contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_9) -> {
127 mimeType = "image/x-nikon-nef"
130 contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_0) -> {
131 mimeType = "image/x-fuji-raf"
134 contentFileName.toUpperCase(Locale.US).contains(MOVIE_SUFFIX) -> {
135 mimeType = "video/mp4"
138 contentFileName.toUpperCase(Locale.US).contains(MOVIE_SUFFIX_MP4) -> {
139 mimeType = "video/mp4"
143 mimeType = "image/jpeg"
148 activity.runOnUiThread {
149 if (!::downloadDialog.isInitialized)
151 downloadDialog = ProgressDialog(activity)
153 downloadDialog.setTitle(activity.getString(R.string.dialog_download_file_title) + appendTitle)
154 downloadDialog.setMessage(activity.getString(R.string.dialog_download_message) + " " + targetFileName)
155 downloadDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
156 downloadDialog.setCancelable(false)
157 downloadDialog.show()
159 val resolver = activity.contentResolver
160 val directoryPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).path + File.separator + activity.getString(R.string.app_name2) + File.separator
161 val calendar = Calendar.getInstance()
162 val extendName = SimpleDateFormat("yyyyMMdd-HHmmss", Locale.getDefault()).format(calendar.time)
163 val periodPosition = targetFileName.indexOf(".")
164 val extension = targetFileName.substring(periodPosition)
165 val baseFileName = targetFileName.substring(0, periodPosition)
166 val outputFileName = baseFileName + "_" + extendName + extension
169 val values = ContentValues()
170 values.put(MediaStore.Images.Media.TITLE, outputFileName)
171 values.put(MediaStore.Images.Media.DISPLAY_NAME, outputFileName)
172 values.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
173 val extStorageUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
174 values.put(MediaStore.Images.Media.RELATIVE_PATH, directoryPath)
175 values.put(MediaStore.Images.Media.IS_PENDING, true)
176 MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
178 values.put(MediaStore.Images.Media.DATA, getExternalOutputDirectory().absolutePath + File.separator + outputFileName)
179 MediaStore.Images.Media.EXTERNAL_CONTENT_URI
181 imageUri = resolver.insert(extStorageUri, values)
182 if (imageUri != null)
184 ////////////////////////////////////////////////////////////////
187 if (imageUri != null)
189 val cursor = resolver.query(imageUri!!, null, null, null, null)
190 DatabaseUtils.dumpCursor(cursor)
194 ////////////////////////////////////////////////////////////////
198 outputStream = resolver.openOutputStream(imageUri!!)
199 val path = fileInfo.contentPath + "/" + contentFileName
200 Log.v(TAG, "downloadContent : $path (small: $isSmallSize)")
201 playbackControl.downloadContent(path, isSmallSize, this)
206 val message = e.message
207 activity.runOnUiThread {
208 downloadDialog.dismiss()
209 isDownloading = false
210 presentMessage(activity.getString(R.string.download_control_save_failed), message)
212 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
214 values.put(MediaStore.Images.Media.IS_PENDING, false)
215 if (imageUri != null)
217 resolver.update(imageUri!!, values, null, null)
232 private fun dismiss()
234 activity.runOnUiThread {
237 downloadDialog.dismiss()
243 isDownloading = false
247 override fun onProgress(bytes: ByteArray?, length: Int, progressEvent: IProgressEvent)
251 val percent = (progressEvent.progress * 100.0f).toInt()
252 downloadDialog.progress = percent
253 if ((outputStream != null)&&(bytes != null)&&(length > 0))
255 outputStream?.write(bytes, 0, length)
264 override fun onCompleted()
268 outputStream?.flush()
269 outputStream?.close()
271 if (imageUri != null)
273 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
275 val values = ContentValues()
276 val resolver = activity.contentResolver
277 values.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
278 values.put(MediaStore.Images.Media.DATA, filepath)
279 values.put(MediaStore.Images.Media.IS_PENDING, false)
280 resolver.update(imageUri!!, values, null, null)
286 if (imageUri != null)
288 activity.runOnUiThread {
289 val preferences = PreferenceManager.getDefaultSharedPreferences(activity)
290 if (preferences.getBoolean(IPreferencePropertyAccessor.SHARE_AFTER_SAVE, false))
292 shareContent(imageUri, mimeType)
296 receiver?.downloadedImage(targetFileName, imageUri)
309 activity.runOnUiThread {
310 downloadDialog.dismiss()
311 isDownloading = false
312 val view = activity.findViewById<View>(R.id.fragment1)
313 Snackbar.make(view, activity.getString(R.string.download_control_save_success) + " " + targetFileName, Snackbar.LENGTH_SHORT).show()
319 val message = e.message
320 activity.runOnUiThread {
321 downloadDialog.dismiss()
322 isDownloading = false
323 presentMessage(activity.getString(R.string.download_control_save_failed), message)
329 override fun onErrorOccurred(e: Exception)
331 isDownloading = false
332 val message = e.message
336 if (outputStream != null)
338 outputStream?.flush()
339 outputStream?.close()
342 catch (ex: Exception)
346 activity.runOnUiThread {
347 downloadDialog.dismiss()
348 isDownloading = false
349 presentMessage(activity.getString(R.string.download_control_download_failed), message)
354 fun isDownloading(): Boolean
362 * @param fileUri ファイルUri
364 private fun shareContent(fileUri: Uri?, contentType: String)
366 val intent = Intent()
367 intent.action = Intent.ACTION_SEND
370 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
371 intent.type = contentType // "video/mp4" or "image/jpeg" or "image/x-adobe-dng"
372 intent.putExtra(Intent.EXTRA_STREAM, fileUri)
373 activity.startActivityForResult(intent, 0)
381 private fun presentMessage(title: String, message: String?)
383 activity.runOnUiThread {
384 val builder = AlertDialog.Builder(activity)
385 builder.setTitle(title).setMessage(message)
392 private val TAG = this.toString()
393 private const val RAW_SUFFIX_1 = ".DNG" // RAW: Ricoh / Pentax
394 private const val RAW_SUFFIX_2 = ".ORF" // RAW: Olympus
395 private const val RAW_SUFFIX_3 = ".PEF" // RAW: Pentax
396 private const val RAW_SUFFIX_4 = ".RW2" // RAW: Panasonic
397 private const val RAW_SUFFIX_5 = ".ARW" // RAW: Sony
398 private const val RAW_SUFFIX_6 = ".CRW" // RAW: Canon
399 private const val RAW_SUFFIX_7 = ".CR2" // RAW: Canon
400 private const val RAW_SUFFIX_8 = ".CR3" // RAW: Canon
401 private const val RAW_SUFFIX_9 = ".NEF" // RAW: Nikon
402 private const val RAW_SUFFIX_0 = ".RAF" // RAW: Fuji
403 private const val RAW_SUFFIX_A = ".RAW" // RAW: Panasonic
404 private const val MOVIE_SUFFIX = ".MOV"
405 private const val MOVIE_SUFFIX_MP4 = ".MP4"
406 private const val JPEG_SUFFIX = ".JPG"