OSDN Git Service

API level 30に引上げ中。
authorMRSa <mrsa@myad.jp>
Tue, 29 Dec 2020 16:11:13 +0000 (01:11 +0900)
committerMRSa <mrsa@myad.jp>
Tue, 29 Dec 2020 16:11:13 +0000 (01:11 +0900)
.idea/jarRepositories.xml
app/build.gradle
app/src/main/java/net/osdn/gokigen/pkremote/playback/MyContentDownloader.kt [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/playback/MyContentDownloaderPrev.java [moved from app/src/main/java/net/osdn/gokigen/pkremote/playback/MyContentDownloader.java with 98% similarity]
build.gradle

index a5f05cd..e34606c 100644 (file)
       <option name="name" value="Google" />
       <option name="url" value="https://dl.google.com/dl/android/maven2/" />
     </remote-repository>
+    <remote-repository>
+      <option name="id" value="MavenRepo" />
+      <option name="name" value="MavenRepo" />
+      <option name="url" value="https://repo.maven.apache.org/maven2/" />
+    </remote-repository>
   </component>
 </project>
\ No newline at end of file
index e247652..2a40c1e 100644 (file)
@@ -1,11 +1,12 @@
 apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
 
 android {
-    compileSdkVersion 29
+    compileSdkVersion 30
     defaultConfig {
         applicationId "net.osdn.gokigen.pkremote"
         minSdkVersion 14
-        targetSdkVersion 29
+        targetSdkVersion 30
         versionCode 10801
         versionName "1.8.1"
     }
@@ -21,10 +22,15 @@ dependencies {
     implementation fileTree(dir: 'libs', include: ['*.jar'])
     implementation 'androidx.appcompat:appcompat:1.2.0'
     implementation 'androidx.exifinterface:exifinterface:1.3.2'
-    implementation 'androidx.preference:preference:1.1.1'
+    implementation 'androidx.preference:preference-ktx:1.1.1'
     implementation 'com.google.android.material:material:1.2.1'
     implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
     implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
 
     api files('libs/olycamerakit.jar')
+    implementation "androidx.core:core-ktx:1.3.2"
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
+repositories {
+    mavenCentral()
 }
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/playback/MyContentDownloader.kt b/app/src/main/java/net/osdn/gokigen/pkremote/playback/MyContentDownloader.kt
new file mode 100644 (file)
index 0000000..6facf9f
--- /dev/null
@@ -0,0 +1,409 @@
+package net.osdn.gokigen.pkremote.playback
+
+import android.app.Activity
+import android.app.AlertDialog
+import android.app.ProgressDialog
+import android.content.ContentValues
+import android.content.Intent
+import android.database.DatabaseUtils
+import android.net.Uri
+import android.os.Build
+import android.os.Environment
+import android.provider.MediaStore
+import android.util.Log
+import android.view.View
+import androidx.preference.PreferenceManager
+import com.google.android.material.snackbar.Snackbar
+import net.osdn.gokigen.pkremote.R
+import net.osdn.gokigen.pkremote.camera.interfaces.playback.ICameraContent
+import net.osdn.gokigen.pkremote.camera.interfaces.playback.IDownloadContentCallback
+import net.osdn.gokigen.pkremote.camera.interfaces.playback.IPlaybackControl
+import net.osdn.gokigen.pkremote.camera.interfaces.playback.IProgressEvent
+import net.osdn.gokigen.pkremote.preference.IPreferencePropertyAccessor
+import java.io.File
+import java.io.OutputStream
+import java.text.SimpleDateFormat
+import java.util.*
+
+class MyContentDownloader(private val activity : Activity, private val playbackControl : IPlaybackControl, private val receiver : IContentDownloadNotify?) : IDownloadContentCallback
+{
+    private lateinit var downloadDialog : ProgressDialog //= ProgressDialog(activity)
+    private val dumpLog = false
+    private var outputStream: OutputStream? = null
+    private var targetFileName = ""
+    private var filepath = ""
+    private var mimeType = "image/jpeg"
+    private var isDownloading = false
+    private var imageUri : Uri? = null
+
+    private fun getExternalOutputDirectory(): File
+    {
+        val directoryPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).path + "/" + activity.getString(R.string.app_name2) + "/"
+        val target = File(directoryPath)
+        try
+        {
+            target.mkdirs()
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+        Log.v(TAG, "  ----- RECORD Directory PATH : $directoryPath -----")
+        return (target)
+    }
+
+    private fun isExternalStorageWritable(): Boolean
+    {
+        return (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED)
+    }
+
+    /**
+     * ダウンロードの開始
+     *
+     */
+    fun startDownload(fileInfo: ICameraContent?, appendTitle: String, replaceJpegSuffix: String?, requestSmallSize: Boolean)
+    {
+        if (fileInfo == null)
+        {
+            Log.v(TAG, "startDownload() ICameraFileInfo is NULL...")
+            return
+        }
+
+        // Download the image.
+        var isSmallSize = requestSmallSize
+        try
+        {
+            isDownloading = true
+            var contentFileName = fileInfo.contentName.toUpperCase(Locale.US)
+            if (replaceJpegSuffix != null)
+            {
+                contentFileName = contentFileName.replace(JPEG_SUFFIX, replaceJpegSuffix)
+                targetFileName = contentFileName
+            }
+            else
+            {
+                targetFileName = fileInfo.originalName.toUpperCase(Locale.US)
+            }
+            Log.v(TAG, "startDownload() $targetFileName")
+            when {
+                contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_1) -> {
+                    mimeType = "image/x-adobe-dng"
+                    isSmallSize = false
+                }
+                contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_2) -> {
+                    mimeType = "image/x-olympus-orf"
+                    isSmallSize = false
+                }
+                contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_3) -> {
+                    mimeType = "image/x-pentax-pef"
+                    isSmallSize = false
+                }
+                contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_4) -> {
+                    mimeType = "image/x-panasonic-rw2"
+                    isSmallSize = false
+                }
+                contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_A) -> {
+                    // Panasonic
+                    mimeType = "image/x-panasonic-raw"
+                    isSmallSize = false
+                }
+                contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_5) -> {
+                    mimeType = "image/x-sony-arw"
+                    isSmallSize = false
+                }
+                contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_6) -> {
+                    mimeType = "image/x-canon-crw"
+                    isSmallSize = false
+                }
+                contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_7) -> {
+                    mimeType = "image/x-canon-cr2"
+                    isSmallSize = false
+                }
+                contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_8) -> {
+                    mimeType = "image/x-canon-cr3"
+                    isSmallSize = false
+                }
+                contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_9) -> {
+                    mimeType = "image/x-nikon-nef"
+                    isSmallSize = false
+                }
+                contentFileName.toUpperCase(Locale.US).contains(RAW_SUFFIX_0) -> {
+                    mimeType = "image/x-fuji-raf"
+                    isSmallSize = false
+                }
+                contentFileName.toUpperCase(Locale.US).contains(MOVIE_SUFFIX) -> {
+                    mimeType = "video/mp4"
+                    isSmallSize = false
+                }
+                contentFileName.toUpperCase(Locale.US).contains(MOVIE_SUFFIX_MP4) -> {
+                    mimeType = "video/mp4"
+                    isSmallSize = false
+                }
+                else -> {
+                    mimeType = "image/jpeg"
+                }
+            }
+
+            ////// ダイアログの表示
+            activity.runOnUiThread {
+                if (!::downloadDialog.isInitialized)
+                {
+                    downloadDialog = ProgressDialog(activity)
+                }
+                downloadDialog.setTitle(activity.getString(R.string.dialog_download_file_title) + appendTitle)
+                downloadDialog.setMessage(activity.getString(R.string.dialog_download_message) + " " + targetFileName)
+                downloadDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
+                downloadDialog.setCancelable(false)
+                downloadDialog.show()
+            }
+            val resolver = activity.contentResolver
+            val directoryPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).path + File.separator + activity.getString(R.string.app_name2) + File.separator
+            val calendar = Calendar.getInstance()
+            val extendName = SimpleDateFormat("yyyyMMdd-HHmmss", Locale.getDefault()).format(calendar.time)
+            val periodPosition = targetFileName.indexOf(".")
+            val extension = targetFileName.substring(periodPosition)
+            val baseFileName = targetFileName.substring(0, periodPosition)
+            val outputFileName = baseFileName + "_" + extendName + extension
+
+
+            val values = ContentValues()
+            values.put(MediaStore.Images.Media.TITLE, outputFileName)
+            values.put(MediaStore.Images.Media.DISPLAY_NAME, outputFileName)
+            values.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
+            val extStorageUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                values.put(MediaStore.Images.Media.RELATIVE_PATH, directoryPath)
+                values.put(MediaStore.Images.Media.IS_PENDING, true)
+                MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
+            } else {
+                values.put(MediaStore.Images.Media.DATA, getExternalOutputDirectory().absolutePath + File.separator + outputFileName)
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+            }
+            imageUri = resolver.insert(extStorageUri, values)
+            if (imageUri != null)
+            {
+                ////////////////////////////////////////////////////////////////
+                if (dumpLog)
+                {
+                    if (imageUri != null)
+                    {
+                        val cursor = resolver.query(imageUri!!, null, null, null, null)
+                        DatabaseUtils.dumpCursor(cursor)
+                        cursor?.close()
+                    }
+                }
+                ////////////////////////////////////////////////////////////////
+
+                try
+                {
+                    outputStream = resolver.openOutputStream(imageUri!!)
+                    val path = fileInfo.contentPath + "/" + contentFileName
+                    Log.v(TAG, "downloadContent : $path (small: $isSmallSize)")
+                    playbackControl.downloadContent(path, isSmallSize, this)
+                }
+                catch (e: Exception)
+                {
+                    e.printStackTrace()
+                    val message = e.message
+                    activity.runOnUiThread {
+                        downloadDialog.dismiss()
+                        isDownloading = false
+                        presentMessage(activity.getString(R.string.download_control_save_failed), message)
+
+                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
+                        {
+                            values.put(MediaStore.Images.Media.IS_PENDING, false)
+                            if (imageUri != null)
+                            {
+                                resolver.update(imageUri!!, values, null, null)
+                            }
+                        }
+                    }
+                }
+
+            }
+        }
+        catch (t: Throwable)
+        {
+            t.printStackTrace()
+            dismiss()
+        }
+    }
+
+    private fun dismiss()
+    {
+        activity.runOnUiThread {
+            try
+            {
+                downloadDialog.dismiss()
+            }
+            catch (e: Exception)
+            {
+                e.printStackTrace()
+            }
+            isDownloading = false
+        }
+    }
+
+    override fun onProgress(bytes: ByteArray?, length: Int, progressEvent: IProgressEvent)
+    {
+        try
+        {
+            val percent = (progressEvent.progress * 100.0f).toInt()
+            downloadDialog.progress = percent
+            if ((outputStream != null)&&(bytes != null)&&(length > 0))
+            {
+                outputStream?.write(bytes, 0, length)
+            }
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    override fun onCompleted()
+    {
+        try
+        {
+            outputStream?.flush()
+            outputStream?.close()
+
+            if (imageUri != null)
+            {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
+                {
+                    val values = ContentValues()
+                    val resolver = activity.contentResolver
+                    values.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
+                    values.put(MediaStore.Images.Media.DATA, filepath)
+                    values.put(MediaStore.Images.Media.IS_PENDING, false)
+                    resolver.update(imageUri!!, values, null, null)
+                }
+            }
+            try
+            {
+
+                if (imageUri != null)
+                {
+                    activity.runOnUiThread {
+                        val preferences = PreferenceManager.getDefaultSharedPreferences(activity)
+                        if (preferences.getBoolean(IPreferencePropertyAccessor.SHARE_AFTER_SAVE, false))
+                        {
+                            shareContent(imageUri, mimeType)
+                        }
+                        try
+                        {
+                            receiver?.downloadedImage(targetFileName, imageUri)
+                        }
+                        catch (e: Exception)
+                        {
+                            e.printStackTrace()
+                        }
+                    }
+                }
+            }
+            catch (e: Exception)
+            {
+                e.printStackTrace()
+            }
+            activity.runOnUiThread {
+                downloadDialog.dismiss()
+                isDownloading = false
+                val view = activity.findViewById<View>(R.id.fragment1)
+                Snackbar.make(view, activity.getString(R.string.download_control_save_success) + " " + targetFileName, Snackbar.LENGTH_SHORT).show()
+                System.gc()
+            }
+        }
+        catch (e: Exception)
+        {
+            val message = e.message
+            activity.runOnUiThread {
+                downloadDialog.dismiss()
+                isDownloading = false
+                presentMessage(activity.getString(R.string.download_control_save_failed), message)
+            }
+        }
+        System.gc()
+    }
+
+    override fun onErrorOccurred(e: Exception)
+    {
+        isDownloading = false
+        val message = e.message
+        try
+        {
+            e.printStackTrace()
+            if (outputStream != null)
+            {
+                outputStream?.flush()
+                outputStream?.close()
+            }
+        }
+        catch (ex: Exception)
+        {
+            ex.printStackTrace()
+        }
+        activity.runOnUiThread {
+            downloadDialog.dismiss()
+            isDownloading = false
+            presentMessage(activity.getString(R.string.download_control_download_failed), message)
+        }
+        System.gc()
+    }
+
+    fun isDownloading(): Boolean
+    {
+        return isDownloading
+    }
+
+    /**
+     * 共有の呼び出し
+     *
+     * @param fileUri  ファイルUri
+     */
+    private fun shareContent(fileUri: Uri?, contentType: String)
+    {
+        val intent = Intent()
+        intent.action = Intent.ACTION_SEND
+        try
+        {
+            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+            intent.type = contentType // "video/mp4"  or "image/jpeg"  or "image/x-adobe-dng"
+            intent.putExtra(Intent.EXTRA_STREAM, fileUri)
+            activity.startActivityForResult(intent, 0)
+        }
+        catch (e: Exception)
+        {
+            e.printStackTrace()
+        }
+    }
+
+    private fun presentMessage(title: String, message: String?)
+    {
+        activity.runOnUiThread {
+            val builder = AlertDialog.Builder(activity)
+            builder.setTitle(title).setMessage(message)
+            builder.show()
+        }
+    }
+
+    companion object
+    {
+        private val TAG = this.toString()
+        private const val RAW_SUFFIX_1 = ".DNG" // RAW: Ricoh / Pentax
+        private const val RAW_SUFFIX_2 = ".ORF" // RAW: Olympus
+        private const val RAW_SUFFIX_3 = ".PEF" // RAW: Pentax
+        private const val RAW_SUFFIX_4 = ".RW2" // RAW: Panasonic
+        private const val RAW_SUFFIX_5 = ".ARW" // RAW: Sony
+        private const val RAW_SUFFIX_6 = ".CRW" // RAW: Canon
+        private const val RAW_SUFFIX_7 = ".CR2" // RAW: Canon
+        private const val RAW_SUFFIX_8 = ".CR3" // RAW: Canon
+        private const val RAW_SUFFIX_9 = ".NEF" // RAW: Nikon
+        private const val RAW_SUFFIX_0 = ".RAF" // RAW: Fuji
+        private const val RAW_SUFFIX_A = ".RAW" // RAW: Panasonic
+        private const val MOVIE_SUFFIX = ".MOV"
+        private const val MOVIE_SUFFIX_MP4 = ".MP4"
+        private const val JPEG_SUFFIX = ".JPG"
+    }
+
+}
\ No newline at end of file
@@ -32,10 +32,10 @@ import androidx.annotation.Nullable;
 import androidx.preference.PreferenceManager;
 
 /**
- *   コンテントのダウンロード
+ *   コンテントのダウンロード (Scoped Storage非対応)
  *
  */
-public class MyContentDownloader implements IDownloadContentCallback
+public class MyContentDownloaderPrev implements IDownloadContentCallback
 {
     private final String TAG = toString();
     private final Activity activity;
@@ -66,7 +66,7 @@ public class MyContentDownloader implements IDownloadContentCallback
      *   コンストラクタ
      *
      */
-    public MyContentDownloader(@NonNull Activity activity, @NonNull final IPlaybackControl playbackControl, @Nullable IContentDownloadNotify receiver)
+    public MyContentDownloaderPrev(@NonNull Activity activity, @NonNull final IPlaybackControl playbackControl, @Nullable IContentDownloadNotify receiver)
     {
         this.activity = activity;
         this.playbackControl = playbackControl;
index 9313af6..0848356 100644 (file)
@@ -1,6 +1,7 @@
 // Top-level build file where you can add configuration options common to all sub-projects/modules.
 
 buildscript {
+    ext.kotlin_version = '1.4.21'
     repositories {
         google()
         jcenter()
@@ -8,7 +9,8 @@ buildscript {
     }
     dependencies {
         classpath 'com.android.tools.build:gradle:4.1.1'
-        
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
     }