Kotlin Android Places API Autocomplete - android

thanks for looking by. As the title suggests I'm facing a rather "weird" issue in my current Kotlin project. In my Application the user can click on a button (or textinput working as a button) and then a new intent pops up where the user can type for a location.
A small snippet here :
R.id.et_location -> {
try{
// This is the list of fields that need to be passed
val fields = listOf(Place.Field.ID, Place.Field.NAME, Place.Field.LAT_LNG, Place.Field.ADDRESS)
// Start the autocomplete intent with a unique request code.
val intent = Autocomplete.IntentBuilder(AutocompleteActivityMode.FULLSCREEN, fields).build(this#AddHappyPlaceActivity)
startActivityForResult(intent, PLACE_AUTOCOMPLETE_REQUEST_CODE)
}catch (e: Exception) {
e.printStackTrace()
Toast.makeText(this, "Couldnt load it", Toast.LENGTH_SHORT).show()}
All that works kinda fine, so the Window pops up but when I start typing something like "Dub" (for Dubai as example) it shows a few entries, but if i type 1-2 characters more it suddenly says "Can't load search results". Sometimes the search doesn't work at all. I've googled that issue and people suggested there could be something wrong with the API Key, but if something was to be wrong with the Key it wouldn't work in the first place and just close the Intent (i tried).
I've tried a few things with the API Key. To begin with i had issues with the first key i created that it didn't work at all. The second key i generated worked partly (my current situation). The first key is restricted to my package/sha1.
The second key as you see has no restrictions at all but it works better than the first one. Im now not sure if its my API Key that causes that issue or my code. Below I'll share code thats relevant.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.sampa.happyPlaces">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Several permissions -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="#string/google_maps_key" />
<activity
android:name=".activities.HappyPlaceDetailActivity"
android:label="HAPPY PLACE DETAILS"
android:screenOrientation="portrait"
android:theme="#style/CustomNoActionBarTheme" />
<activity
android:name=".activities.AddHappyPlaceActivity"
android:label="ADD HAPPY PLACE"
android:screenOrientation="portrait"
android:theme="#style/CustomNoActionBarTheme" />
<activity android:name=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
google_maps_api.xml (I deleted my SHA1key and API Key before posting here)
<resources>
<!--
TODO: Before you run your application, you need a Google Maps API key.
To get one, follow this link, follow the directions and press "Create" at the end:
https://console.developers.google.com/flows/enableapi?apiid=maps_android_backend&keyType=CLIENT_SIDE_ANDROID&r=60:BF:DE:FE:3C:6F:DA:3B:56:09:E7:B7:BB:5F:FC:F8:AE:00:DC:0D%3Beu.sampa.happyPlaces.activities
You can also add your credentials to an existing key, using these values:
Package name:
eu.sampa.happyPlaces.activities
SHA-1 certificate fingerprint:
MY SHA1 KEY
Alternatively, follow the directions here:
https://developers.google.com/maps/documentation/android/start#get-key
Once you have your key (it starts with "AIza"), replace the "google_maps_key"
string in this file.
-->
<string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">MY API KEY</string>
</resources>
And now at last my Activity where all necessary stuff is processed (sorry for the length... but I've commented all functions regarding this issue with the comment "// For the Places API", just use the search function if necessary)
AddHappyPlaceActivity.kt
package eu.sampa.happyPlaces.activities
import android.Manifest
import android.app.Activity
import android.app.DatePickerDialog
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.provider.Settings
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.android.libraries.places.api.Places
import com.google.android.libraries.places.api.model.Place
import com.google.android.libraries.places.widget.Autocomplete
import com.google.android.libraries.places.widget.model.AutocompleteActivityMode
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import eu.sampa.happyPlaces.R
import eu.sampa.happyPlaces.database.DatabaseHandler
import eu.sampa.happyPlaces.models.HappyPlaceModel
import kotlinx.android.synthetic.main.activity_add_happy_place.*
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStream
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.*
class AddHappyPlaceActivity : AppCompatActivity(), View.OnClickListener {
// Creates a variable for GALLERY Selection which will be later used in the onActivityResult method.
companion object {
private const val GALLERY = 1
private const val CAMERA = 2
private const val IMAGE_DIRECTORY = "HappyPlacesImages"
private const val PLACE_AUTOCOMPLETE_REQUEST_CODE = 3
}
private var saveImageToInternalStorage : Uri? = null
private var mLatitude : Double = 0.0
private var mLongitude : Double = 0.0
// For the swipe feature
private var mHappyPlaceDetails : HappyPlaceModel? = null
// Creating the variables of Calender Instance and DatePickerDialog listener to use it for date selection
// A variable to get an instance calendar using the default time zone and locale.
private var cal = Calendar.getInstance()
/* A variable for DatePickerDialog OnDateSetListener.
* The listener used to indicate the user has finished selecting a date. It will be initialized later. */
private lateinit var dateSetListener : DatePickerDialog.OnDateSetListener
// Used to increment when someone clicks on the Add Photo button see below in onClick function
private var addButtonClicked = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_happy_place)
// Adds the back button on the ActionBar
setSupportActionBar(toolbar_add_place)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
toolbar_add_place.setNavigationOnClickListener {
onBackPressed()
}
// For the Places API
if(!Places.isInitialized()) {
Places.initialize(this#AddHappyPlaceActivity, resources.getString(R.string.google_maps_key))
}
if(intent.hasExtra(MainActivity.EXTRA_PLACE_DETAILS)) {
mHappyPlaceDetails = intent.getParcelableExtra(MainActivity.EXTRA_PLACE_DETAILS) as HappyPlaceModel
}
// Initialize the DatePicker and sets the selected date
// https://www.tutorialkart.com/kotlin-android/android-datepicker-kotlin-example/
dateSetListener = DatePickerDialog.OnDateSetListener{
_, year, month, dayOfMonth ->
cal.set(Calendar.YEAR, year)
cal.set(Calendar.MONTH, month)
cal.set(Calendar.DAY_OF_MONTH, dayOfMonth)
updateDateInView()
}
// Automatically sets the current date
updateDateInView()
// Uses functionality in the onClick function below
et_date.setOnClickListener(this)
tv_add_image.setOnClickListener(this)
btn_save.setOnClickListener(this)
et_location.setOnClickListener(this)
if(mHappyPlaceDetails != null) {
supportActionBar?.title = "Edit Happy PLace"
et_title.setText(mHappyPlaceDetails!!.title)
et_description.setText(mHappyPlaceDetails!!.description)
et_date.setText(mHappyPlaceDetails!!.date)
et_location.setText(mHappyPlaceDetails!!.location)
mLatitude = mHappyPlaceDetails!!.latitude
mLongitude = mHappyPlaceDetails!!.longitude
saveImageToInternalStorage = Uri.parse(mHappyPlaceDetails!!.image)
iv_place_image.setImageURI(saveImageToInternalStorage)
btn_save.text = "UPDATE"
}
}
// This is a override method after extending the onclick listener interface (gets created automatically)
override fun onClick(v: View?) {
when (v!!.id) {
R.id.et_date -> {
DatePickerDialog(this#AddHappyPlaceActivity, dateSetListener,
cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)).show()
}
R.id.tv_add_image -> {
val pictureDialog = AlertDialog.Builder(this)
pictureDialog.setTitle("Select Action")
val pictureDialogItems = arrayOf("Select photo from gallery", "Capture photo from camera")
pictureDialog.setItems(pictureDialogItems) {
_, which ->
when(which) {
0 -> choosePhotoFromGallery()
1 -> takePhotoFromCamera()
}
}
pictureDialog.show()
/* Used to display the Dialog to get to the menu after the user
* denied access 2 or more times */
addButtonClicked += 1
if (addButtonClicked > 2) {
if (ContextCompat.checkSelfPermission(this#AddHappyPlaceActivity,
Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
showRationalDialogForPermissions()
}
if (ContextCompat.checkSelfPermission(this#AddHappyPlaceActivity,
Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
showRationalDialogForPermissions()
}
if (ContextCompat.checkSelfPermission(this#AddHappyPlaceActivity,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
showRationalDialogForPermissions()
}
}
}
R.id.btn_save -> {
when {
et_title.text.isNullOrEmpty() -> {
Toast.makeText(this, "Please enter title", Toast.LENGTH_SHORT).show()
}
et_description.text.isNullOrEmpty() -> {
Toast.makeText(this, "Please enter description", Toast.LENGTH_SHORT)
.show()
}
et_location.text.isNullOrEmpty() -> {
Toast.makeText(this, "Please select location", Toast.LENGTH_SHORT)
.show()
}
saveImageToInternalStorage == null -> {
Toast.makeText(this, "Please add image", Toast.LENGTH_SHORT).show()
}
else -> {
// Assigning all the values to data model class.
val happyPlaceModel = HappyPlaceModel(
if(mHappyPlaceDetails == null) 0 else mHappyPlaceDetails!!.id,
et_title.text.toString(),
saveImageToInternalStorage.toString(),
et_description.text.toString(),
et_date.text.toString(),
et_location.text.toString(),
mLatitude,
mLongitude
)
// Here we initialize the database handler class.
val dbHandler = DatabaseHandler(this)
if (mHappyPlaceDetails == null) {
val addHappyPlace = dbHandler.addHappyPlace(happyPlaceModel)
if (addHappyPlace > 0) {
setResult(Activity.RESULT_OK)
finish() // Gets us back to MainActivity
}
} else{
val updateHappyPlace = dbHandler.updateHappyPlace(happyPlaceModel)
// greater than zero indicates that everything worked out
if (updateHappyPlace > 0) {
setResult(Activity.RESULT_OK)
finish() // Gets us back to MainActivity
}
}
}
}
}
// For the Places API
R.id.et_location -> {
try{
// This is the list of fields that need to be passed
val fields = listOf(Place.Field.ID, Place.Field.NAME, Place.Field.LAT_LNG, Place.Field.ADDRESS)
// Start the autocomplete intent with a unique request code.
val intent = Autocomplete.IntentBuilder(AutocompleteActivityMode.FULLSCREEN, fields).build(this#AddHappyPlaceActivity)
startActivityForResult(intent, PLACE_AUTOCOMPLETE_REQUEST_CODE)
}catch (e: Exception) {
e.printStackTrace()
Toast.makeText(this, "Couldnt load it", Toast.LENGTH_SHORT).show()}
}
}
}
// Method used for taking pictures with the Camera
private fun takePhotoFromCamera() {
// Asking for permissions using DEXTER Library
Dexter.withContext(this).withPermissions(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
).withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
// Here after all the permission are granted launch the Camera to capture an image
val galleryIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
startActivityForResult(galleryIntent, CAMERA )
}
override fun onPermissionRationaleShouldBeShown(permissions: MutableList<PermissionRequest>?, token: PermissionToken?) {
token?.continuePermissionRequest()
}
}).onSameThread().check()
}
// Method used for image selection from GALLERY/PHOTOS
private fun choosePhotoFromGallery() {
// Asking for permissions using DEXTER Library
Dexter.withContext(this).withPermissions(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
).withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
// Here after all the permission are granted, launch the gallery to select and image.
val galleryIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
startActivityForResult(galleryIntent,
GALLERY
)
}
override fun onPermissionRationaleShouldBeShown(permissions: MutableList<PermissionRequest>?, token: PermissionToken?) {
token?.continuePermissionRequest()
}
}).onSameThread().check()
}
// Message to be shown if user denies access and possibly send him to the settings
private fun showRationalDialogForPermissions() {
AlertDialog.Builder(this).setMessage("It looks like you have turned off " +
"permissions required for this feature").setPositiveButton("GO TO SETTINGS")
{ _, _ ->
try{
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
val uri = Uri.fromParts("package", packageName, null)
intent.data = uri
startActivity(intent)
} catch (e: ActivityNotFoundException) {
e.printStackTrace()
}
}.setNegativeButton("Cancel") { dialog, _ ->
dialog.dismiss()
}.show()
}
// Handles the chosen Image from the startActivityResult from choosePhotoFromGallery and takePhotoFromCamera
#RequiresApi(Build.VERSION_CODES.P)
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(resultCode == Activity.RESULT_OK) {
if(requestCode == GALLERY) {
if(data != null) {
val contentURI = data.data
// For more info go to https://stackoverflow.com/questions/56651444/deprecated-getbitmap-with-api-29-any-alternative-codes
try {
if(Build.VERSION.SDK_INT < 28) {
// Here this is used to get an bitmap from URI
val selectedImageBitmap = MediaStore.Images.Media.getBitmap(this.contentResolver, contentURI)
// Saving an image which is selected from GALLERY. And printed the path in logcat
saveImageToInternalStorage = saveImageToInternalStorage(selectedImageBitmap)
Log.e("Saved image: ", "Path :: $saveImageToInternalStorage")
iv_place_image!!.setImageBitmap(selectedImageBitmap) // Set the selected image from GALLERY to imageView
} else {
val selectedImageBitmapSource = contentURI?.let { ImageDecoder.createSource(this.contentResolver, it) }
val selectedImageBitmap = selectedImageBitmapSource?.let { ImageDecoder.decodeBitmap(it) }
// Saving an image which is selected from GALLERY. And printed the path in logcat
saveImageToInternalStorage = selectedImageBitmap?.let { saveImageToInternalStorage(it) }
Log.e("Saved image: ", "Path :: $saveImageToInternalStorage")
iv_place_image.setImageBitmap(selectedImageBitmap)
}
} catch (e: IOException) {
e.printStackTrace()
Toast.makeText(this#AddHappyPlaceActivity, "Failed to load the Image!", Toast.LENGTH_SHORT).show()
}
}
// Camera result will be received here
} else if(requestCode == CAMERA){
val thumbNail : Bitmap = data!!.extras!!.get("data") as Bitmap // Bitmap from camera
// Saving an image which is selected from CAMERA. And printed the path in logcat
saveImageToInternalStorage = saveImageToInternalStorage(thumbNail)
Log.e("Saved image: ", "Path :: $saveImageToInternalStorage")
iv_place_image.setImageBitmap(thumbNail) // Set to the imageView
// For the Places API
} else if(requestCode == PLACE_AUTOCOMPLETE_REQUEST_CODE) {
val place : Place = Autocomplete.getPlaceFromIntent(data!!)
et_location.setText(place.address)
mLatitude = place.latLng!!.latitude
mLongitude = place.latLng!!.longitude
}
}
}
// A function to update the selected date in the UI with selected format.
private fun updateDateInView() {
val myFormat = "dd.MM.yyyy"
val sdf = SimpleDateFormat(myFormat, Locale.getDefault())
et_date.setText(sdf.format(cal.time).toString())
}
/* https://android--code.blogspot.com/2018/04/android-kotlin-save-image-to-internal.html
Uri gives us the location back */
private fun saveImageToInternalStorage(bitmap: Bitmap):Uri {
// Get the context wrapper instance
val wrapper = ContextWrapper(applicationContext)
// This line returns a directory in the internal storage
var file = wrapper.getDir(IMAGE_DIRECTORY, Context.MODE_PRIVATE)
// First we give the location and then we generate a random Name for the Image
file = File(file, "${UUID.randomUUID()}.jpg")
//
try {
val stream : OutputStream = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.JPEG,100, stream)
stream.flush()
stream.close()
}catch (e: IOException) {
e.printStackTrace()
}
// Return the saved image uri
return Uri.parse(file.absolutePath)
}
}

The behavior behind inconsistent results for Google Places AutoComplete is you are missing of Billing Account.
Login Google Cloud Platform
Head to Billing menu
If you have a previous billing account, you can assign it to your Google Cloud project. If not, create a new billing account using your debit card.
4)Try billing account API KEY, it will work fine
Check this solution, and I think it would work perfectly and your results will be consistent.
Happy Coding 🤓

Related

Upload recorded file to AWS S3 storage or google drive

I have created an app to record the audio in the android wear but I am not able to transfer that recorded file to mobile phone as there is no dedicated file manager in Samsung galaxy watch 4. So for that I need to upload the the recorded file to the AWS S3 STORAGE.
Here is the code for audio recording.
package com.example.watch
import android.Manifest.permission
import android.annotation.SuppressLint
import android.content.ContentValues
import android.content.Intent
import android.content.pm.PackageManager
import android.media.MediaRecorder
import android.net.Uri
import android.os.*
import android.provider.MediaStore
import android.util.Log
import android.view.WindowManager
import android.widget.Button
import android.widget.Chronometer
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import com.amplifyframework.core.Amplify
import com.amplifyframework.storage.StorageException
import com.amplifyframework.storage.result.StorageUploadFileResult
import com.example.watch.databinding.ActivityMicrophoneBinding
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import java.text.SimpleDateFormat
import java.util.*
#Suppress("DEPRECATION")
class Microphone : AppCompatActivity() {
// Initializing all variables..
private var Stop: Button? = null
private var Start: Button? = null
private var statusTV:TextView? = null
private var Chronometer:TextView?=null
var fileName: String? = null
var audioRecorder: MediaRecorder? = null
var audiouri: Uri? = null
var file: ParcelFileDescriptor? = null
lateinit var status:TextView
private var binding: ActivityMicrophoneBinding? = null
private lateinit var chronometer: Chronometer
private var audioFile: File? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_microphone)
if (!CheckPermissions())
RequestPermissions()
AmplifyInit().intializeAmplify(this#Microphone)
this.window.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
)
supportActionBar!!.hide()
// initialize all variables with their layout items.
Start=binding!!.accelerometerStartButton
Stop=binding!!.accelerometerStopButton
status=binding!!.Status
chronometer=binding!!.chronometer
Start!!.setOnClickListener { // start recording method will
// start the recording of audio.
startRecording()
Start!!.isEnabled=false
status.text="Recording..."
chronometer.base = SystemClock.elapsedRealtime()
chronometer.start()
startService()
}
Stop!!.setOnClickListener { // pause Recording method will
// pause the recording of audio.
pauseRecording()
Start!!.isEnabled=true
status.text="Recording Stopped"
chronometer.stop()
uploadFile()
stopService()
}
}
private fun startRecording() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
val values = ContentValues(4)
values.put(MediaStore.Audio.Media.TITLE, fileName)
values.put(
MediaStore.Audio.Media.DATE_ADDED,
SimpleDateFormat("yyyyMMddHHmmss").format(Date())
)
values.put(MediaStore.Audio.Media.RELATIVE_PATH, "Music/Recordings/")
audiouri = contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values)
file = contentResolver.openFileDescriptor(audiouri!!, "w")
if (file != null) {
audioRecorder = MediaRecorder()
audioRecorder!!.setAudioSource(MediaRecorder.AudioSource.MIC)
audioRecorder!!.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
audioRecorder!!.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
audioRecorder!!.setOutputFile(file!!.fileDescriptor)
audioRecorder!!.setAudioChannels(1)
audioRecorder!!.prepare()
audioRecorder!!.start()
audioRecorder!!.setOutputFile(getAudioFile().absolutePath)
}
}
else {
RequestPermissions()
}
}
private fun getAudioFile(): File {
if (audioFile == null) {
audioFile = File(getExternalFilesDir(null), "Music/Recordings/")
}
return audioFile!!
}
#SuppressLint("MissingSuperCall")
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray,
) {
// this method is called when user will
// grant the permission for audio recording.
when (requestCode) {
REQUEST_AUDIO_PERMISSION_CODE -> {
if (grantResults.isNotEmpty()) {
val permissionToRecord = grantResults[0] == PackageManager.PERMISSION_GRANTED
val permissionToStore = grantResults[1] == PackageManager.PERMISSION_GRANTED
if (permissionToRecord && permissionToStore) {
Toast.makeText(applicationContext, "Permission Granted", Toast.LENGTH_LONG)
.show()
} else {
Toast.makeText(applicationContext, "Permission Denied", Toast.LENGTH_LONG)
.show()
}
}
}
}
}
private fun CheckPermissions(): Boolean {
// this method is used to check permission
val result = ContextCompat.checkSelfPermission(applicationContext, permission.WRITE_EXTERNAL_STORAGE)
val result1 = ContextCompat.checkSelfPermission(applicationContext, permission.RECORD_AUDIO)
return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED
}
private fun RequestPermissions() {
// this method is used to request the
// permission for audio recording and storage.
ActivityCompat.requestPermissions(this,
arrayOf(permission.RECORD_AUDIO, permission.WRITE_EXTERNAL_STORAGE),
REQUEST_AUDIO_PERMISSION_CODE
)
}
private fun pauseRecording() {
Start!!.isEnabled=true
stopService(Intent(this, ForegroundService::class.java))
// below method will stop
// the audio recording.
try {
audioRecorder!!.stop()
} catch (stopException: RuntimeException) {
// handle cleanup here
}
}
fun startService() {
val serviceIntent = Intent(this, ForegroundService::class.java)
serviceIntent.putExtra("inputExtra", "Foreground Service Example in Android")
ContextCompat.startForegroundService(this, serviceIntent)
}
fun stopService() {
val serviceIntent = Intent(this, ForegroundService::class.java)
stopService(serviceIntent)
}
companion object {
// string variable is created for storing a file name
private var mFileName: String? = null
var permissionAccepted = false
var permissions = arrayOf(android.Manifest.permission.RECORD_AUDIO,
android.Manifest.permission.READ_EXTERNAL_STORAGE,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
// constant for storing audio permission
const val REQUEST_AUDIO_PERMISSION_CODE = 1000
}
private fun uploadFile() {
Amplify.Storage.uploadFile(
"Audio/audio.mp3",
getAudioFile(),
{ result: StorageUploadFileResult ->
Log.i(
"MyAmplifyApp",
"Successfully uploaded: " + result.key
)
Toast.makeText(this, "File has Successfully Uploaded:" , Toast.LENGTH_SHORT).show()
}
) { storageFailure: StorageException? ->
Log.e(
"MyAmplifyApp",
"Upload failed",
storageFailure
)
Toast.makeText(this, "Upload failed", Toast.LENGTH_SHORT).show()
}
}
}
Now how can i modify my uploadfile function to upload the fiole to AWS S3, As i am getting this error.
E/amplify:aws-s3-storage:SinglePartUploadWorker: SinglePartUploadWorker failed with exception: kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job=JobImpl{Cancelled}#9b17179
I/WM-WorkerWrapper: Worker result RETRY for Work [ id=48cd3651-35e2-456e-9329-81930f5277a5, tags={ UPLOAD, awsS3StoragePlugin, 9, com.amplifyframework.storage.s3.transfer.worker.RouterWorker } ]
I am using dependecies:
implementation 'com.amplifyframework:aws-api:2.1.0'
implementation 'com.amplifyframework:aws-datastore:2.1.0'
implementation 'com.amplifyframework:aws-storage-s3:2.1.0'
implementation 'com.amplifyframework:aws-auth-cognito:2.1.0'
Library:
<uses-library
android:name="com.google.android.wearable"
android:required="true" />

Android 11 : How to write at 30 files per seconds on removable storage (ssd drive on usb)

I want to save images taken from my app directly to a ssd drive (removable storage) plugged in my device.
The issue I have now, is that with Android 11, I didn't manage to get the path of this storage, and so I can't write the files...
I tried use Storage Access Framework to ask the user to specify the path directly for each images but I can't use this solution as I need to write 30 images per seconds and it kept asking the user select an action on the screen.
This application is only for internal use, so I can grant all the permission without any Google deployment politics issues.
Can anybody help me, i'm so desperate...
So here's my code, I can write on a folder the user choose with SAF. Still have speed issue using DocumentFile.createFile function.
package com.example.ssdwriter
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.*
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.documentfile.provider.DocumentFile
class MainActivity : AppCompatActivity() {
private val TAG = "SSDActivity"
private val CONTENT = ByteArray(2 * 1024 * 1024)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
grantDirectoryAccess()
}
private fun grantDirectoryAccess() {
val treeUri = contentResolver.persistedUriPermissions
if (treeUri.size > 0) {
Log.e(TAG, treeUri.size.toString())
startWriting(treeUri[0].uri)
} else {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
var resultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
result.data?.data?.let {
contentResolver.takePersistableUriPermission(
it,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
}
startWriting(result.data?.data!!)
}
}
resultLauncher.launch(intent)
}
}
private fun startWriting(uri: Uri) {
var handlerThread = HandlerThread("writer")
handlerThread.start()
var counter = 0
val handler = Handler(handlerThread.looper)
val runnableCode: Runnable = object : Runnable {
override fun run() {
Log.e(TAG, "Writing File $counter")
createFile(uri, counter++)
Log.e(TAG, "File $counter written ")
if(counter <= 150){
handler.postDelayed(this, 33)
}
}
}
handler.post(runnableCode)
}
private fun createFile(treeUri: Uri, counter: Int) {
val dir = DocumentFile.fromTreeUri(this, treeUri)
val file = dir!!.createFile("*/bmp", "Test$counter.bmp")
if (file != null) {
var outputStream = contentResolver.openOutputStream(file.uri)
if (outputStream != null) {
outputStream.write(CONTENT)
outputStream.flush()
outputStream.close()
}
}
}
}
If anyone got some clues to make this faster, it would be great !

Video Capture (MediaStore) & Upload To FireBase - Android Kotlin

Its my first kotlin project and its been 3 months now since I started. I'm making a social app. Now I'm having difficulty in writing the code for recording and uploading video file on Firebase Storage and Database. Below is my code for the Video Post Creation Activity which handles the uploading and publishing. I tried many modifications but the app crashes on this code now whenever I tap on creating a new post or uploading a video. Can you please check what's wrong here. As my intent is only recording a video, and writing food name, place etc and then publishing the post. I'm actually confused on my code.
import android.app.Activity
import android.app.ProgressDialog
import android.content.Intent
import android.database.Cursor
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.text.TextUtils
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.tasks.Continuation
import com.google.android.gms.tasks.OnCompleteListener
import com.google.android.gms.tasks.Task
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.storage.FirebaseStorage
import com.google.firebase.storage.StorageReference
import com.google.firebase.storage.StorageTask
import com.google.firebase.storage.UploadTask
import kotlinx.android.synthetic.main.activity_add_post.*
class AddPostActivity : AppCompatActivity() {
private var myUrl = ""
private var videoUri: Uri? = null
private var storagePostPicRef: StorageReference? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_post)
storagePostPicRef = FirebaseStorage.getInstance().reference.child("Posts Videos")
save_new_post_btn.setOnClickListener { uploadImage() }
intent.action = MediaStore.ACTION_VIDEO_CAPTURE
startActivityForResult(intent, 101)
// var intent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
// startActivityForResult(intent, VIDEO_CAPTURE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 101 && resultCode == Activity.RESULT_OK && data != null) {
val result = videoUri
video_new_post.setVideoURI(videoUri)
}
}
private fun getPath(uri: Uri): String {
var projectionArray = arrayOf(MediaStore.Video.Media.DATA)
var cursor: Cursor? =
applicationContext.contentResolver.query(uri, projectionArray, null, null, null)
if (cursor != null) {
val columnIndex: Int = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
cursor.moveToFirst()
return cursor.getString(columnIndex)
} else {
return ""
}
}
private fun uploadImage() {
when {
videoUri == null -> Toast.makeText(this, "Please select a Video!", Toast.LENGTH_LONG)
.show()
TextUtils.isEmpty(name_new_post.text.toString()) -> Toast.makeText(
this,
"Please fill all the fields!",
Toast.LENGTH_LONG
).show()
TextUtils.isEmpty(place_new_post.text.toString()) -> Toast.makeText(
this,
"Please fill all the fields!",
Toast.LENGTH_LONG
).show()
TextUtils.isEmpty(city_new_post.text.toString()) -> Toast.makeText(
this,
"Please fill all the fields!",
Toast.LENGTH_LONG
).show()
TextUtils.isEmpty(state_new_post.text.toString()) -> Toast.makeText(
this,
"Please fill all the fields!",
Toast.LENGTH_LONG
).show()
else -> {
val progressDialog = ProgressDialog(this)
progressDialog.setTitle("Upload Started...")
progressDialog.setMessage("Great! Please wait until we bake it...")
progressDialog.show()
val fileRef =
storagePostPicRef!!.child(System.currentTimeMillis().toString() + ".mp4")
var uploadTask: StorageTask<*>
uploadTask = fileRef.putFile(videoUri!!)
uploadTask.continueWithTask(Continuation<UploadTask.TaskSnapshot, Task<Uri>> { task ->
if (!task.isSuccessful) {
task.exception?.let {
throw it
progressDialog.dismiss()
}
}
return#Continuation fileRef.downloadUrl
})
.addOnCompleteListener(OnCompleteListener<Uri> { task ->
if (task.isSuccessful) {
val downloadUrl = task.result
myUrl = downloadUrl.toString()
val ref = FirebaseDatabase.getInstance().reference.child("Posts")
val postId = ref.push().key
val postMap = HashMap<String, Any>()
postMap["postid"] = postId!!
postMap["foodname"] = name_new_post.text.toString().toLowerCase()
postMap["placename"] = place_new_post.text.toString().toLowerCase()
postMap["cityname"] = city_new_post.text.toString().toLowerCase()
postMap["statename"] = state_new_post.text.toString().toLowerCase()
postMap["publisher"] = FirebaseAuth.getInstance().currentUser!!.uid
postMap["postimage"] = myUrl
ref.child(postId).updateChildren(postMap)
Toast.makeText(this, "Upload Finished Successfully!", Toast.LENGTH_LONG)
.show()
val intent = Intent(this#AddPostActivity, MainActivity::class.java)
startActivity(intent)
finish()
progressDialog.dismiss()
} else {
progressDialog.dismiss()
}
})
}
}
}
}
So after adjusting my code and restructuring it, this is what I came up with, and its still not opening anything, just closes the app when tried to create a post. It should open up the camera for recording though.
import android.app.ProgressDialog
import android.content.Intent
import android.database.Cursor
import android.icu.text.SimpleDateFormat
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.text.TextUtils
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import com.google.android.gms.tasks.Continuation
import com.google.android.gms.tasks.OnCompleteListener
import com.google.android.gms.tasks.Task
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.storage.FirebaseStorage
import com.google.firebase.storage.StorageReference
import com.google.firebase.storage.StorageTask
import com.google.firebase.storage.UploadTask
import kotlinx.android.synthetic.main.activity_add_post.*
import java.io.File
import java.io.IOException
class AddPostActivity : AppCompatActivity() {
private var myUrl = ""
private var videoUri: Uri? = null
private var storagePostPicRef: StorageReference? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_post)
storagePostPicRef = FirebaseStorage.getInstance().reference.child("Posts Videos")
save_new_post_btn.setOnClickListener { uploadImage() }
dispatchTakeVideoIntent()
}
fun createVideoFile(): File {
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(java.util.Date())
val storageDir: File? = getExternalFilesDir(Environment.DIRECTORY_MOVIES)
return File.createTempFile(
"Video_${timeStamp}_", /* prefix */
".mp4", /* suffix */
storageDir /* directory */
)
}
val REQUEST_VIDEO_CAPTURE = 101
private fun dispatchTakeVideoIntent() {
Intent(MediaStore.ACTION_VIDEO_CAPTURE).also { takeVideoIntent ->
takeVideoIntent.resolveActivity(packageManager)?.also {
val videoFile: File? = try {
createVideoFile()
} catch (ex: IOException) {
null
}
// Continue only if the File was successfully created
videoFile?.also {
videoUri = FileProvider.getUriForFile(
this,
"your_app_id.provider", // add your application id , copy from build.gradle file paste here
it
)
takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, videoUri)
startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE)
}
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 101 && resultCode == RESULT_OK) {
Log.d("VideoPath", videoUri?.path) // you can see video path in log
uploadImage()
}
}
private fun uploadImage() {
when {
videoUri == null -> Toast.makeText(this, "Please select a Video!", Toast.LENGTH_LONG)
.show()
TextUtils.isEmpty(name_new_post.text.toString()) -> Toast.makeText(
this,
"Please fill all the fields!",
Toast.LENGTH_LONG
).show()
TextUtils.isEmpty(place_new_post.text.toString()) -> Toast.makeText(
this,
"Please fill all the fields!",
Toast.LENGTH_LONG
).show()
TextUtils.isEmpty(city_new_post.text.toString()) -> Toast.makeText(
this,
"Please fill all the fields!",
Toast.LENGTH_LONG
).show()
TextUtils.isEmpty(state_new_post.text.toString()) -> Toast.makeText(
this,
"Please fill all the fields!",
Toast.LENGTH_LONG
).show()
else -> {
val progressDialog = ProgressDialog(this)
progressDialog.setTitle("Upload Started...")
progressDialog.setMessage("Great! Please wait until we bake it...")
progressDialog.show()
val fileRef =
storagePostPicRef!!.child(System.currentTimeMillis().toString() + ".mp4")
fileRef.putFile(videoUri!!).addOnSuccessListener {
fileRef.downloadUrl.addOnSuccessListener {
myUrl = it.toString()
val ref = FirebaseDatabase.getInstance().reference.child("Posts")
val postId = ref.push().key
val postMap = HashMap<String, Any>()
postMap["postid"] = postId!!
postMap["foodname"] = name_new_post.text.toString().toLowerCase()
postMap["placename"] = place_new_post.text.toString().toLowerCase()
postMap["cityname"] = city_new_post.text.toString().toLowerCase()
postMap["statename"] = state_new_post.text.toString().toLowerCase()
postMap["publisher"] = FirebaseAuth.getInstance().currentUser!!.uid
postMap["postimage"] = myUrl
ref.child(postId).setValue(postMap)
Toast.makeText(this, "Upload Finished Successfully!", Toast.LENGTH_LONG)
.show()
progressDialog.dismiss()
/* val intent = Intent(this#AddPostActivity, MainActivity::class.java)
startActivity(intent)
finish()*/
// if you want to return MainActivity just use finish()
finish()
}
}.addOnFailureListener {
progressDialog.dismiss()
}
}
}
}
}
We are using getUriForFile(Context, String, File) which returns a
content:// URI. For more recent apps targeting Android 7.0 (API level
24) and higher, passing a file:// URI across a package boundary causes
a FileUriExposedException. Therefore, we now present a more generic
way of storing images using a FileProvider.
We create path file like this: res/xml/file_paths.xml
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="external_files" path="." />
</paths>
Represents files in the root of your app's external storage area.
Add provider in manifest.xml
<manifest>
...
<application>
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
...
</application>
</manifest>
Here's a function that invokes an intent to capture video.
val REQUEST_VIDEO_CAPTURE = 101
private fun dispatchTakeVideoIntent() {
Intent(MediaStore.ACTION_VIDEO_CAPTURE).also { takeVideoIntent ->
takeVideoIntent.resolveActivity(packageManager)?.also {
val videoFile: File? = try {
createVideoFile()
} catch (ex: IOException) {
null
}
// Continue only if the File was successfully created
videoFile?.also {
videoUri = FileProvider.getUriForFile(
this,
"${your_app_id}.provider",
it
)
takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, videoUri)
startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE)
}
}
}
}
Here's an example solution in a method that returns a unique file name for a new video using a date-time stamp:
#Throws(IOException::class)
private fun createVideoFile(): File {
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(java.util.Date())
val storageDir: File? = getExternalFilesDir(Environment.DIRECTORY_MOVIES)
return File.createTempFile(
"Video_${timeStamp}_", /* prefix */
".mp4", /* suffix */
storageDir /* directory */
)
}
Continue if the process is successfully
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_VIDEO_CAPTURE && resultCode == RESULT_OK) {
Log.d("VideoPath",videoURI?.path) // you can see video path in log
uploadImage()
}
}
D/VideoPath:
/external_files/Movies/Video_20200510_143935_5711184798783973477.mp4
Finally, we make a change in the image upload function. We need to make this change to get the image download url:
if (task.isSuccessful) {
fileRef.downloadUrl().addOnSuccessListener {
myUrl = it.toString()
val ref = FirebaseDatabase.getInstance().reference.child("Posts")
val postId = ref.push().key
val postMap = HashMap<String, Any>()
postMap["postid"] = postId!!
postMap["foodname"] = name_new_post.text.toString().toLowerCase()
postMap["placename"] = place_new_post.text.toString().toLowerCase()
postMap["cityname"] = city_new_post.text.toString().toLowerCase()
postMap["statename"] = state_new_post.text.toString().toLowerCase()
postMap["publisher"] = FirebaseAuth.getInstance().currentUser!!.uid
postMap["postimage"] = myUrl
ref.child(postId).updateChildren(postMap)
Toast.makeText(this, "Upload Finished Successfully!", Toast.LENGTH_LONG)
.show()
progressDialog.dismiss()
/* val intent = Intent(this#AddPostActivity, MainActivity::class.java)
startActivity(intent)
finish()*/
// if you want to return MainActivity just use finish()
finish()
}
} else {
progressDialog.dismiss()
}

Kotlin Android Full Sized Camera instead of Thumbnail?

thanks for looking by! I've pretty much finished my little application i've been working on. I tried the app on my phone and found an error that I wasn't aware of when using the emulator (rookie mistake I suppose). So when the app is started the user can add like a "happy place" that looks the following:
the issue starts when the user wants to add a picture that he wants to take with the Camera itself. (That itself works). Taking the picture and also displaying it in that small box works (partly). But here is also my issue. After trying the app out on the phone i realised that the taken picture had terrible quality. I googled that issue and found out that i have used a method that only get's me the thumbnail on not the actual full sized picture. I googled more to find out how i can get that to work, unfortnunately i only found the documentation and i couldn't really figure it out either way what i had to change.
Here is my code that contains all info regarding the camera functionality:
package eu.sampa.happyPlaces.activities
import android.Manifest
import android.app.Activity
import android.app.DatePickerDialog
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.ImageDecoder
import android.location.Location
import android.location.LocationManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Looper
import android.provider.MediaStore
import android.provider.Settings
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.android.gms.location.*
import com.google.android.libraries.places.api.Places
import com.google.android.libraries.places.api.model.Place
import com.google.android.libraries.places.widget.Autocomplete
import com.google.android.libraries.places.widget.model.AutocompleteActivityMode
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import eu.sampa.happyPlaces.R
import eu.sampa.happyPlaces.database.DatabaseHandler
import eu.sampa.happyPlaces.models.HappyPlaceModel
import eu.sampa.happyPlaces.utils.GetAddressFromLatLng
import kotlinx.android.synthetic.main.activity_add_happy_place.*
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStream
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.*
class AddHappyPlaceActivity : AppCompatActivity(), View.OnClickListener {
// Creates a variable for GALLERY Selection which will be later used in the onActivityResult method.
companion object {
private const val GALLERY = 1
private const val CAMERA = 2
private const val IMAGE_DIRECTORY = "HappyPlacesImages"
private const val PLACE_AUTOCOMPLETE_REQUEST_CODE = 3
}
private var saveImageToInternalStorage : Uri? = null
private var mLatitude : Double = 0.0
private var mLongitude : Double = 0.0
// For the swipe feature
private var mHappyPlaceDetails : HappyPlaceModel? = null
// Will be initialized later for the get current position functionality
// https://medium.com/#droidbyme/get-current-location-using-fusedlocationproviderclient-in-android-cb7ebf5ab88e
private lateinit var mFusedLocationClient: FusedLocationProviderClient
// Creating the variables of Calender Instance and DatePickerDialog listener to use it for date selection
// A variable to get an instance calendar using the default time zone and locale.
private var cal = Calendar.getInstance()
/* A variable for DatePickerDialog OnDateSetListener.
* The listener used to indicate the user has finished selecting a date. It will be initialized later. */
private lateinit var dateSetListener : DatePickerDialog.OnDateSetListener
// Used to increment when someone clicks on the Add Photo button see below in onClick function
private var addButtonClicked = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_happy_place)
// Adds the back button on the ActionBar
setSupportActionBar(toolbar_add_place)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
toolbar_add_place.setNavigationOnClickListener {
onBackPressed()
}
// For the Places API
if(!Places.isInitialized()) {
Places.initialize(this#AddHappyPlaceActivity, resources.getString(R.string.google_maps_key))
}
if(intent.hasExtra(MainActivity.EXTRA_PLACE_DETAILS)) {
mHappyPlaceDetails = intent.getParcelableExtra(MainActivity.EXTRA_PLACE_DETAILS) as HappyPlaceModel
}
// Initialize the DatePicker and sets the selected date
// https://www.tutorialkart.com/kotlin-android/android-datepicker-kotlin-example/
dateSetListener = DatePickerDialog.OnDateSetListener{
_, year, month, dayOfMonth ->
cal.set(Calendar.YEAR, year)
cal.set(Calendar.MONTH, month)
cal.set(Calendar.DAY_OF_MONTH, dayOfMonth)
updateDateInView()
}
// Automatically sets the current date
updateDateInView()
// Uses functionality in the onClick function below
et_date.setOnClickListener(this)
tv_add_image.setOnClickListener(this)
btn_save.setOnClickListener(this)
et_location.setOnClickListener(this)
tv_select_current_location.setOnClickListener(this)
if(mHappyPlaceDetails != null) {
supportActionBar?.title = "Edit Happy PLace"
et_title.setText(mHappyPlaceDetails!!.title)
et_description.setText(mHappyPlaceDetails!!.description)
et_date.setText(mHappyPlaceDetails!!.date)
et_location.setText(mHappyPlaceDetails!!.location)
mLatitude = mHappyPlaceDetails!!.latitude
mLongitude = mHappyPlaceDetails!!.longitude
saveImageToInternalStorage = Uri.parse(mHappyPlaceDetails!!.image)
iv_place_image.setImageURI(saveImageToInternalStorage)
btn_save.text = "UPDATE"
}
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
}
// This is a override method after extending the onclick listener interface (gets created automatically)
override fun onClick(v: View?) {
when (v!!.id) {
R.id.et_date -> {
DatePickerDialog(this#AddHappyPlaceActivity, dateSetListener,
cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)).show()
}
R.id.tv_add_image -> {
val pictureDialog = AlertDialog.Builder(this)
pictureDialog.setTitle("Select Action")
val pictureDialogItems = arrayOf("Select photo from gallery", "Capture photo from camera")
pictureDialog.setItems(pictureDialogItems) {
_, which ->
when(which) {
0 -> choosePhotoFromGallery()
1 -> takePhotoFromCamera()
}
}
pictureDialog.show()
/* Used to display the Dialog to get to the menu after the user
* denied access 2 or more times */
addButtonClicked += 1
if (addButtonClicked > 2) {
if (ContextCompat.checkSelfPermission(this#AddHappyPlaceActivity,
Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
showRationalDialogForPermissions()
}
if (ContextCompat.checkSelfPermission(this#AddHappyPlaceActivity,
Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
showRationalDialogForPermissions()
}
if (ContextCompat.checkSelfPermission(this#AddHappyPlaceActivity,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
showRationalDialogForPermissions()
}
}
}
R.id.btn_save -> {
when {
et_title.text.isNullOrEmpty() -> {
Toast.makeText(this, "Please enter title", Toast.LENGTH_SHORT).show()
}
et_description.text.isNullOrEmpty() -> {
Toast.makeText(this, "Please enter description", Toast.LENGTH_SHORT)
.show()
}
et_location.text.isNullOrEmpty() -> {
Toast.makeText(this, "Please select location", Toast.LENGTH_SHORT)
.show()
}
saveImageToInternalStorage == null -> {
Toast.makeText(this, "Please add image", Toast.LENGTH_SHORT).show()
}
else -> {
// Assigning all the values to data model class.
val happyPlaceModel = HappyPlaceModel(
if(mHappyPlaceDetails == null) 0 else mHappyPlaceDetails!!.id,
et_title.text.toString(),
saveImageToInternalStorage.toString(),
et_description.text.toString(),
et_date.text.toString(),
et_location.text.toString(),
mLatitude,
mLongitude
)
// Here we initialize the database handler class.
val dbHandler = DatabaseHandler(this)
if (mHappyPlaceDetails == null) {
val addHappyPlace = dbHandler.addHappyPlace(happyPlaceModel)
if (addHappyPlace > 0) {
setResult(Activity.RESULT_OK)
finish() // Gets us back to MainActivity
}
} else{
val updateHappyPlace = dbHandler.updateHappyPlace(happyPlaceModel)
// greater than zero indicates that everything worked out
if (updateHappyPlace > 0) {
setResult(Activity.RESULT_OK)
finish() // Gets us back to MainActivity
}
}
}
}
}
// For the Places API
R.id.et_location -> {
try{
// This is the list of fields that need to be passed
val fields = listOf(Place.Field.ID, Place.Field.NAME, Place.Field.LAT_LNG, Place.Field.ADDRESS)
// Start the autocomplete intent with a unique request code.
val intent = Autocomplete.IntentBuilder(AutocompleteActivityMode.FULLSCREEN, fields).build(this#AddHappyPlaceActivity)
startActivityForResult(intent, PLACE_AUTOCOMPLETE_REQUEST_CODE)
}catch (e: Exception) {
e.printStackTrace()
Toast.makeText(this, "Couldnt load it", Toast.LENGTH_SHORT).show()}
}
R.id.tv_select_current_location -> {
if (!isLocationEnabled()) {
Toast.makeText(this, "Your location provider is turned off. Please turn it on.", Toast.LENGTH_SHORT).show()
// This will redirect the user to settings from where you need to turn on the location provider.
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
startActivity(intent)
} else {
// https://www.androdocs.com/kotlin/getting-current-location-latitude-longitude-in-android-using-kotlin.html
Dexter.withActivity(this)
.withPermissions(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION).withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
if (report!!.areAllPermissionsGranted()) {
requestNewLocationData()
}
}
override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>?,
token: PermissionToken?
) {
showRationalDialogForPermissions()
}
}).onSameThread()
.check()
}
}
}
}
// https://stackoverflow.com/questions/51313359/get-current-location-android-kotlin
private fun isLocationEnabled(): Boolean {
val locationManager: LocationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER )
}
// Method used for taking pictures with the Camera
private fun takePhotoFromCamera() {
// Asking for permissions using DEXTER Library
Dexter.withContext(this).withPermissions(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
).withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
// Here after all the permission are granted launch the Camera to capture an image
val galleryIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
startActivityForResult(galleryIntent, CAMERA )
}
override fun onPermissionRationaleShouldBeShown(permissions: MutableList<PermissionRequest>?, token: PermissionToken?) {
token?.continuePermissionRequest()
}
}).onSameThread().check()
}
// Method used for image selection from GALLERY/PHOTOS
private fun choosePhotoFromGallery() {
// Asking for permissions using DEXTER Library
Dexter.withContext(this).withPermissions(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
).withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
// Here after all the permission are granted, launch the gallery to select and image.
val galleryIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
startActivityForResult(galleryIntent,
GALLERY
)
}
override fun onPermissionRationaleShouldBeShown(permissions: MutableList<PermissionRequest>?, token: PermissionToken?) {
token?.continuePermissionRequest()
}
}).onSameThread().check()
}
// Message to be shown if user denies access and possibly send him to the settings
private fun showRationalDialogForPermissions() {
AlertDialog.Builder(this).setMessage("It looks like you have turned off " +
"permissions required for this feature").setPositiveButton("GO TO SETTINGS")
{ _, _ ->
try{
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
val uri = Uri.fromParts("package", packageName, null)
intent.data = uri
startActivity(intent)
} catch (e: ActivityNotFoundException) {
e.printStackTrace()
}
}.setNegativeButton("Cancel") { dialog, _ ->
dialog.dismiss()
}.show()
}
// Handles the chosen Image from the startActivityResult from choosePhotoFromGallery and takePhotoFromCamera
#RequiresApi(Build.VERSION_CODES.P)
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(resultCode == Activity.RESULT_OK) {
if(requestCode == GALLERY) {
if(data != null) {
val contentURI = data.data
// For more info go to https://stackoverflow.com/questions/56651444/deprecated-getbitmap-with-api-29-any-alternative-codes
try {
if(Build.VERSION.SDK_INT < 28) {
// Here this is used to get an bitmap from URI
val selectedImageBitmap = MediaStore.Images.Media.getBitmap(this.contentResolver, contentURI)
// Saving an image which is selected from GALLERY. And printed the path in logcat
saveImageToInternalStorage = saveImageToInternalStorage(selectedImageBitmap)
Log.e("Saved image: ", "Path :: $saveImageToInternalStorage")
iv_place_image!!.setImageBitmap(selectedImageBitmap) // Set the selected image from GALLERY to imageView
} else {
val selectedImageBitmapSource = contentURI?.let { ImageDecoder.createSource(this.contentResolver, it) }
val selectedImageBitmap = selectedImageBitmapSource?.let { ImageDecoder.decodeBitmap(it) }
// Saving an image which is selected from GALLERY. And printed the path in logcat
saveImageToInternalStorage = selectedImageBitmap?.let { saveImageToInternalStorage(it) }
Log.e("Saved image: ", "Path :: $saveImageToInternalStorage")
iv_place_image.setImageBitmap(selectedImageBitmap)
}
} catch (e: IOException) {
e.printStackTrace()
Toast.makeText(this#AddHappyPlaceActivity, "Failed to load the Image!", Toast.LENGTH_SHORT).show()
}
}
// Camera result will be received here
} else if(requestCode == CAMERA){
val thumbNail : Bitmap = data!!.extras!!.get("data") as Bitmap // Bitmap from camera
// Saving an image which is selected from CAMERA. And printed the path in logcat
saveImageToInternalStorage = saveImageToInternalStorage(thumbNail)
Log.e("Saved image: ", "Path :: $saveImageToInternalStorage")
iv_place_image.setImageBitmap(thumbNail) // Set to the imageView
// For the Places API
} else if(requestCode == PLACE_AUTOCOMPLETE_REQUEST_CODE) {
val place : Place = Autocomplete.getPlaceFromIntent(data!!)
et_location.setText(place.address)
mLatitude = place.latLng!!.latitude
mLongitude = place.latLng!!.longitude
}
}
}
// A function to update the selected date in the UI with selected format.
private fun updateDateInView() {
val myFormat = "dd.MM.yyyy"
val sdf = SimpleDateFormat(myFormat, Locale.getDefault())
et_date.setText(sdf.format(cal.time).toString())
}
/* https://android--code.blogspot.com/20 18/04/android-kotlin-save-image-to-internal.html
Uri gives us the location back */
private fun saveImageToInternalStorage(bitmap: Bitmap):Uri {
// Get the context wrapper instance
val wrapper = ContextWrapper(applicationContext)
// This line returns a directory in the internal storage
var file = wrapper.getDir(IMAGE_DIRECTORY, Context.MODE_PRIVATE)
// First we give the location and then we generate a random Name for the Image
file = File(file, "${UUID.randomUUID()}.jpg")
//
try {
val stream : OutputStream = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.JPEG,100, stream)
stream.flush()
stream.close()
}catch (e: IOException) {
e.printStackTrace()
}
// Return the saved image uri
return Uri.parse(file.absolutePath)
}
private fun requestNewLocationData() {
var mLocationRequest = LocationRequest()
mLocationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
mLocationRequest.interval = 1000
mLocationRequest.numUpdates = 1
mFusedLocationClient.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.myLooper())
}
private val mLocationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
val mLastLocation: Location = locationResult.lastLocation
mLatitude = mLastLocation.latitude
Log.e("Current Latitude", "$mLatitude")
mLongitude = mLastLocation.longitude
Log.e("Current Longitude", "$mLongitude")
val addressTask = GetAddressFromLatLng(this#AddHappyPlaceActivity, mLatitude, mLongitude)
addressTask.setAddressListener(object : GetAddressFromLatLng.AddressListener {
override fun onAddressFound(address: String?) {
Log.e("Address ::", "" + address)
et_location.setText(address) // Address is set to the edittext
}
override fun onError() {
Log.e("Get Address ::", "Something is wrong...")
}
})
addressTask.executeGetAddress()
}
}
}

Persistable permissions URI android Q

I'm using the emulator and I send the intent in this way:
StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Intent i;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
i = sm.getPrimaryStorageVolume().createOpenDocumentTreeIntent();
} else {
return null;
}
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
i.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
i.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
and in onActivityResult():
Uri uri = data.getData();
if (uri == null)
return null;
context.getContentResolver()
.takePersistableUriPermission(uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Every time I reboot, the app doesn't have access. Can anyone confirm about this bug on beta 6 on the emulator? Am I doing anything wrong?
I converted your code snippets into this Kotlin activity:
package com.commonsware.jetpack.myapplication
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Bundle
import android.os.storage.StorageManager
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.documentfile.provider.DocumentFile
private const val PREF_URI = "uri"
private const val REQUEST_SAF = 1337
class MainActivity : AppCompatActivity() {
private val prefs: SharedPreferences by lazy {
getSharedPreferences("test", Context.MODE_PRIVATE)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val uriString = prefs.getString(PREF_URI, null)
val storageManager = getSystemService(StorageManager::class.java)!!
if (uriString == null) {
startActivityForResult(
storageManager.primaryStorageVolume.createOpenDocumentTreeIntent(),
REQUEST_SAF
)
} else {
val uri = Uri.parse(uriString)
val docFile = DocumentFile.fromTreeUri(this, uri)
Toast.makeText(
this,
"canRead: ${docFile?.canRead()} canWrite: ${docFile?.canWrite()}",
Toast.LENGTH_LONG
).show()
}
}
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?
) {
val uri = data?.data
if (uri == null) {
Toast.makeText(this, "Did not get a Uri??!?", Toast.LENGTH_LONG).show()
} else {
contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
prefs.edit().putString(PREF_URI, uri.toString()).apply()
Toast.makeText(this, "OK, run the activity again", Toast.LENGTH_LONG).show()
}
finish()
}
}
I then ran the app containing this activity as its launcher activity and chose Downloads/ as the document tree. Subsequent launches of this activity — both before and after a reboot — show canRead() and canWrite() both return true. This was tested on a Google Pixel running Q Beta 6.
I cannot try this on a Q emulator because Google, so I cannot confirm if I can reproduce your behavior there. But, the fact that this works as expected on hardware should be a positive sign.
For those who appreciate the accepted answer, but do not want to use startActivityForResult, which was deprecated after the original answer was accepted.
My example shows how to save a file to storage from within your app.
First, create a class-wide variable to hold the ActivityResultLauncher information:
private lateinit var saveDataResultLauncher: ActivityResultLauncher<String>
Then, instead of using the deprecated feature, you can put this, for example, in your MainActivity's onCreate fun:
/** Backup Website Entries */
saveDataResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument()) {}
Make the user choose a location to save a file, after pressing a menu item bound to the functionality implemented above:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_save_file -> {
saveDataResultLauncher.launch("example.txt")
true
}
else -> super.onOptionsItemSelected(item)
}
}

Categories

Resources