apply plugin: 'kotlin-android-extensions'
android {
- compileSdkVersion 29
+ compileSdkVersion 30
defaultConfig {
applicationId "jp.osdn.gokigen.mangle"
minSdkVersion 21
- targetSdkVersion 29
+ targetSdkVersion 30
versionCode 1000000
versionName "1.0.0"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'androidx.preference:preference:1.1.1'
- implementation 'com.google.android.material:material:1.2.0'
+ implementation 'com.google.android.material:material:1.2.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
package jp.osdn.gokigen.mangle
import android.Manifest
+import android.content.Intent
import android.content.pm.PackageManager
+import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.WindowManager
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import jp.osdn.gokigen.mangle.scene.MainButtonHandler
-import jp.osdn.gokigen.mangle.scene.ShowMessage
import jp.osdn.gokigen.mangle.scene.SceneChanger
+import jp.osdn.gokigen.mangle.scene.ShowMessage
class MainActivity : AppCompatActivity()
{
private val TAG = toString()
private val mainButtonHandler : MainButtonHandler = MainButtonHandler(this)
private val showMessage : ShowMessage = ShowMessage(this)
-
- //private var cameraControl: CameraControl? = null
private val sceneChanger : SceneChanger = SceneChanger(this, showMessage)
- //private var sceneChanger : SceneChanger? = null // = SceneChanger(this, this)
override fun onCreate(savedInstanceState: Bundle?)
{
supportActionBar?.hide()
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
- //sceneChanger = SceneChanger(this, this)
- //cameraControl = CameraControl(this)
if (allPermissionsGranted())
{
- //cameraControl?.startCamera()
+ checkMediaWritePermission()
sceneChanger.initializeFragment()
mainButtonHandler.initialize()
}
{
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
}
- //cameraControl?.initialize()
}
override fun onDestroy()
{
super.onDestroy()
sceneChanger.finish()
- //cameraControl?.finish()
-
- //sceneChanger = null
}
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
}
+ private fun checkMediaWritePermission()
+ {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
+ {
+ StorageOperationWithPermission(this).requestAndroidRMediaPermission()
+ }
+ }
+
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray)
{
if (requestCode == REQUEST_CODE_PERMISSIONS)
{
if (allPermissionsGranted())
{
- //cameraControl?.startCamera()
+ checkMediaWritePermission()
sceneChanger.initializeFragment()
mainButtonHandler.initialize()
}
}
}
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
+ {
+ super.onActivityResult(requestCode, resultCode, data)
+ if ((requestCode == REQUEST_CODE_MEDIA_EDIT)&&(resultCode == RESULT_OK))
+ {
+ //
+ Log.v(TAG, " WRITE PERMISSION GRANTED")
+ }
+ }
+
companion object
{
private const val REQUEST_CODE_PERMISSIONS = 10
- private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
+ const val REQUEST_CODE_MEDIA_EDIT = 12
+ private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA,
+ Manifest.permission.VIBRATE,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
}
--- /dev/null
+package jp.osdn.gokigen.mangle
+
+import android.content.ContentResolver
+import android.net.Uri
+import android.os.Build
+import android.provider.MediaStore
+import androidx.annotation.RequiresApi
+import androidx.appcompat.app.AppCompatActivity
+
+/**
+ *
+ *
+ */
+@RequiresApi(api = Build.VERSION_CODES.R)
+class StorageOperationWithPermission(val activity: AppCompatActivity)
+{
+ fun requestAndroidRMediaPermission()
+ {
+ val urisToModify: List<Uri> = listOf(
+ MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+ )
+ val contentResolver : ContentResolver = activity.contentResolver
+ val editPendingIntent = MediaStore.createWriteRequest(contentResolver, urisToModify)
+
+ activity.startIntentSenderForResult(editPendingIntent.intentSender, MainActivity.REQUEST_CODE_MEDIA_EDIT,null, 0, 0, 0)
+ }
+}
package jp.osdn.gokigen.mangle.operation
import android.util.Log
+import android.util.Size
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.Preview
fun initialize()
{
Log.v(TAG, " initialize()")
- fileControl.initialize()
cameraExecutor = Executors.newSingleThreadExecutor()
}
try
{
val imageAnalyzer = ImageAnalysis.Builder()
+ .setTargetResolution(Size(800, 600))
+ .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also {
it.setAnalyzer(cameraExecutor, MyImageAnalyzer { luma ->
e.printStackTrace()
}
}, ContextCompat.getMainExecutor(activity))
+
}
fun finish()
package jp.osdn.gokigen.mangle.operation
+import android.content.ContentValues
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 android.widget.Button
import androidx.camera.core.ImageCaptureException
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
+import androidx.preference.PreferenceManager
import com.google.android.material.snackbar.Snackbar
import jp.osdn.gokigen.mangle.R
+import jp.osdn.gokigen.mangle.preference.IPreferencePropertyAccessor
import java.io.File
-import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.*
{
private val TAG = toString()
private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
-
- private lateinit var outputDirectory: File
private var imageCapture: ImageCapture? = null
+ //private lateinit var outputDirectory: File
+ //private var isLocalLocation : Boolean = false
+
init
{
}
- fun initialize()
- {
- outputDirectory = getOutputDirectory()
- context.findViewById<Button>(R.id.camera_capture_button)?.setOnClickListener( this )
- }
-
fun prepare() : ImageCapture?
{
try
{
+ context.findViewById<Button>(R.id.camera_capture_button)?.setOnClickListener(this)
imageCapture = ImageCapture.Builder().build()
}
- catch (e : Exception)
+ catch (e: Exception)
{
e.printStackTrace()
}
}
- private fun getOutputDirectory(): File
+ /**
+ * 保存用ディレクトリを準備する(ダメな場合はアプリ領域のディレクトリを確保する)
+ *
+ */
+ private fun prepareLocalOutputDirectory(): File
{
- val mediaDir = context.externalMediaDirs.firstOrNull()?.let {
- File(it, context.resources.getString(R.string.app_name)).apply { mkdirs() }
- }
+ val mediaDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
+ mediaDir?.mkdirs()
return (if (mediaDir != null && mediaDir.exists()) mediaDir else context.filesDir)
}
- private fun takePhoto()
+ private fun takePhotoLocal()
{
- Log.v(TAG, " takePhoto()")
-
val imageCapture = imageCapture ?: return
- val photoFile = File(outputDirectory, SimpleDateFormat(FILENAME_FORMAT, Locale.US).format(System.currentTimeMillis()) + ".jpg")
+
+ Log.v(TAG, " takePhotoLocal()")
+ val photoFile = File(prepareLocalOutputDirectory(), SimpleDateFormat(FILENAME_FORMAT, Locale.US).format(System.currentTimeMillis()) + ".jpg")
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
imageCapture.takePicture(
- outputOptions, ContextCompat.getMainExecutor(context), object : ImageCapture.OnImageSavedCallback {
- override fun onError(exc: ImageCaptureException) {
+ outputOptions,
+ ContextCompat.getMainExecutor(context),
+ object : ImageCapture.OnImageSavedCallback
+ {
+ override fun onError(exc: ImageCaptureException)
+ {
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
}
- override fun onImageSaved(output: ImageCapture.OutputFileResults) {
+ override fun onImageSaved(output: ImageCapture.OutputFileResults)
+ {
val savedUri = Uri.fromFile(photoFile)
val msg = context.getString(R.string.capture_success) + " $savedUri"
//Toast.makeText(context.baseContext, msg, Toast.LENGTH_SHORT).show()
- Snackbar.make(context.findViewById<androidx.constraintlayout.widget.ConstraintLayout>(
- R.id.main_layout
- ), msg, Snackbar.LENGTH_SHORT).show()
+ Snackbar.make(
+ context.findViewById<androidx.constraintlayout.widget.ConstraintLayout>(
+ R.id.main_layout
+ ), msg, Snackbar.LENGTH_SHORT
+ ).show()
Log.v(TAG, msg)
}
- })
+ }
+ )
+ }
+
+ private fun getExternalOutputDirectory(): File
+ {
+ val directoryPath = (Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).path + "/" + context.getString(R.string.app_location) + "/").toLowerCase(Locale.US)
+ val target = File(directoryPath)
+ try
+ {
+ target.mkdirs()
+ }
+ catch (e: Exception)
+ {
+ e.printStackTrace()
+ }
+ Log.v(TAG, " ----- RECORD Directory PATH : $directoryPath -----")
+ return (target)
+ }
+
+ private fun takePhotoExternal()
+ {
+ val imageCapture = imageCapture ?: return
+ if (!isExternalStorageWritable())
+ {
+ takePhotoLocal()
+ return
+ }
+ Log.v(TAG, " takePhotoExternal()")
+
+ val outputDir = getExternalOutputDirectory()
+ val resolver = context.contentResolver
+
+ val mimeType = "image/jpeg"
+ val now = System.currentTimeMillis()
+ val path = "DCIM/aira01a"
+ val photoFile = SimpleDateFormat(FILENAME_FORMAT, Locale.US).format(now) + ".jpg"
+
+ val extStorageUri : Uri
+ val values = ContentValues()
+ values.put(MediaStore.Images.Media.DISPLAY_NAME,photoFile)
+ values.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
+ values.put(MediaStore.Images.Media.DATE_ADDED, now)
+ values.put(MediaStore.Images.Media.DATE_MODIFIED, now)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
+ {
+ values.put(MediaStore.Images.Media.DATA, photoFile)
+ values.put(MediaStore.Images.Media.DATE_TAKEN, now)
+ values.put(MediaStore.Images.Media.RELATIVE_PATH, path)
+ values.put(MediaStore.Images.Media.IS_PENDING, true)
+ extStorageUri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY + "/" + photoFile)
+ }
+ else
+ {
+ extStorageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+ }
+
+ val imageUri = resolver.insert(extStorageUri, values)
+ if (imageUri != null)
+ {
+ val openStream = resolver.openOutputStream(imageUri)
+ if (openStream != null)
+ {
+ val outputOptions = ImageCapture.OutputFileOptions.Builder(openStream).build()
+ imageCapture.takePicture(
+ outputOptions,
+ ContextCompat.getMainExecutor(context),
+ object : ImageCapture.OnImageSavedCallback
+ {
+ override fun onError(exc: ImageCaptureException)
+ {
+ Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
+ {
+ values.clear()
+ values.put(MediaStore.Images.Media.IS_PENDING, false)
+ resolver.update(imageUri, values, null, null)
+ }
+ }
+
+ override fun onImageSaved(output: ImageCapture.OutputFileResults)
+ {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
+ {
+ //values.clear()
+ values.put(MediaStore.Images.Media.IS_PENDING, false)
+ resolver.update(imageUri, values, null, null)
+ }
+ val msg = context.getString(R.string.capture_success) + " $extStorageUri $path $photoFile"
+ //Toast.makeText(context.baseContext, msg, Toast.LENGTH_SHORT).show()
+ Snackbar.make(
+ context.findViewById<androidx.constraintlayout.widget.ConstraintLayout>(
+ R.id.main_layout
+ ), msg, Snackbar.LENGTH_SHORT
+ ).show()
+ Log.v(TAG, msg)
+ }
+ })
+ }
+ }
+ }
+
+ private fun takePhoto()
+ {
+ Log.v(TAG, " takePhoto()")
+ try
+ {
+ val isLocalLocation = PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
+ IPreferencePropertyAccessor.PREFERENCE_SAVE_LOCAL_LOCATION,
+ IPreferencePropertyAccessor.PREFERENCE_SAVE_LOCAL_LOCATION_DEFAULT_VALUE
+ )
+ if (isLocalLocation)
+ {
+ takePhotoLocal()
+ }
+ else
+ {
+ takePhotoExternal()
+ }
+ }
+ catch (e: Exception)
+ {
+ e.printStackTrace()
+ }
+ }
+
+ private fun isExternalStorageWritable(): Boolean
+ {
+ return (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED)
+ }
+
+/*
+ private fun isExternalStorageReadable(): Boolean
+ {
+ return (Environment.getExternalStorageState() in setOf(
+ Environment.MEDIA_MOUNTED,
+ Environment.MEDIA_MOUNTED_READ_ONLY
+ ))
}
+*/
override fun onClick(v: View?)
{
+ Log.v(TAG, " onClick : $v?.id ")
when (v?.id)
{
R.id.camera_capture_button -> takePhoto()
else -> Log.v(TAG, " Unknown ID : " + v?.id)
}
}
-}
\ No newline at end of file
+}
companion object
{
- val PREFERENCE_NOTIFICATIONS = "show_notifications"
- val PREFERENCE_NOTIFICATIONS_DEFAULT_VALUE : Boolean = false
+ // --- PREFERENCE KEY AND DEFAULT VALUE ---
+ const val PREFERENCE_NOTIFICATIONS = "show_notifications"
+ const val PREFERENCE_NOTIFICATIONS_DEFAULT_VALUE = false
+ const val PREFERENCE_SAVE_LOCAL_LOCATION = "save_local_location"
+ const val PREFERENCE_SAVE_LOCAL_LOCATION_DEFAULT_VALUE = false
// --- SCREEN TRANSACTION LABEL ---
- val LABEL_EXIT_APPLICATION = "exit_application"
- val LABEL_WIFI_SETTINGS = "wifi_settings"
- val LABEL_INSTRUCTION_LINK = "instruction_link"
- val LABEL_PRIVACY_POLICY = "privacy_policy"
- val LABEL_DEBUG_INFO = "debug_info"
+ const val LABEL_EXIT_APPLICATION = "exit_application"
+ const val LABEL_WIFI_SETTINGS = "wifi_settings"
+ const val LABEL_INSTRUCTION_LINK = "instruction_link"
+ const val LABEL_PRIVACY_POLICY = "privacy_policy"
+ const val LABEL_DEBUG_INFO = "debug_info"
}
}
Log.v(TAG, " onAttach() : ")
preferences = PreferenceManager.getDefaultSharedPreferences(context)
- PreferenceInitializer().initializePreferences(preferences)
+ PreferenceValueInitializer().initializePreferences(preferences)
preferences.registerOnSharedPreferenceChangeListener(this)
}
- override fun onResume()
- {
- super.onResume()
- Log.v(TAG, " onResume() : ")
- }
-
- override fun onPause()
- {
- super.onPause()
- Log.v(TAG, " onPause() : ")
- }
-
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?)
{
var value = false
when (key)
{
IPreferencePropertyAccessor.PREFERENCE_NOTIFICATIONS -> value = preferences.getBoolean(key, IPreferencePropertyAccessor.PREFERENCE_NOTIFICATIONS_DEFAULT_VALUE)
+ IPreferencePropertyAccessor.PREFERENCE_SAVE_LOCAL_LOCATION -> value = preferences.getBoolean(key, IPreferencePropertyAccessor.PREFERENCE_SAVE_LOCAL_LOCATION_DEFAULT_VALUE)
// else -> Log.v(TAG, " onSharedPreferenceChanged() : + $key ")
}
Log.v(TAG, " onSharedPreferenceChanged() : + $key, $value")
import android.content.SharedPreferences
-class PreferenceInitializer
+class PreferenceValueInitializer
{
fun initializePreferences(preferences : SharedPreferences)
{
{
editor.putBoolean(IPreferencePropertyAccessor.PREFERENCE_NOTIFICATIONS, IPreferencePropertyAccessor.PREFERENCE_NOTIFICATIONS_DEFAULT_VALUE)
}
+ if (!items.containsKey(IPreferencePropertyAccessor.PREFERENCE_SAVE_LOCAL_LOCATION))
+ {
+ editor.putBoolean(IPreferencePropertyAccessor.PREFERENCE_SAVE_LOCAL_LOCATION, IPreferencePropertyAccessor.PREFERENCE_SAVE_LOCAL_LOCATION_DEFAULT_VALUE)
+ }
editor.apply()
}
return (previewView)
}
previewView = inflater.inflate(R.layout.camera_capture, null, false)
- //cameraControl?.initialize()
return (previewView)
}
{
private val TAG = toString()
private val cameraControl: CameraControl = CameraControl(activity)
- private var count : Int = 0
+
private lateinit var previewFragment : PreviewFragment
private lateinit var logCatFragment : LogCatFragment
private lateinit var mainPreferenceFragment : MainPreferenceFragment
previewFragment = PreviewFragment.newInstance()
previewFragment.setCameraControl(cameraControl)
}
- cameraControl.startCamera()
setDefaultFragment(previewFragment)
-
- count++
- informationNotify.updateMessage(" changeToPreview " + count, false, true, Color.BLUE)
+ cameraControl.startCamera()
}
override fun changeToPreview()
<resources>
<string name="blank"> </string>
- <string name="app_name">mangle</string>
+ <string name="app_name">A01e</string>
+ <string name="app_location">AirA01a</string>
<string name="action_refresh">更新</string>
<string name="finish_refresh">更新終了</string>
<string name="dialog_positive_execute">OK</string>
<string name="pref_wifi_settings">WiFi設定</string>
<string name="pref_summary_wifi_settings"> </string>
+ <string name="pref_cat_application_settings">アプリ設定</string>
+ <string name="save_local_location">アプリ固有領域に保存</string>
+
<string name="dialog_title_exit_application">アプリケーション終了</string>
<string name="dialog_message_exit_application">アプリケーションを終了します。</string>
<resources>
<string name="blank"> </string>
- <string name="app_name">mangle</string>
+ <string name="app_name">A01e</string>
+ <string name="app_location">AirA01a</string>
<string name="action_refresh">Refresh</string>
<string name="finish_refresh">Finish refresh.</string>
<string name="dialog_positive_execute">OK</string>
<string name="pref_wifi_settings">WIFI Settings</string>
<string name="pref_summary_wifi_settings"> </string>
+ <string name="pref_cat_application_settings">App. Settings</string>
+ <string name="save_local_location">Save local location</string>
+
<string name="dialog_title_exit_application">Exit Application</string>
<string name="dialog_message_exit_application">Exit Application</string>
android:key="wifi_settings"
android:title="@string/pref_wifi_settings"
android:summary="@string/pref_summary_wifi_settings" />
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ app:title="@string/pref_cat_application_settings">
+<!--
<SwitchPreferenceCompat
android:key="show_notifications"
android:title="@string/notification_title"/>
+-->
+ <SwitchPreferenceCompat
+ android:key="save_local_location"
+ android:title="@string/save_local_location"/>
</PreferenceCategory>
<PreferenceCategory