Skip to content

Commit f32c4d4

Browse files
committed
* Fix crash in Android 10 after disable Storage permission and open app
* Update permission request flow to make sure storage permission is asked before accessing data
1 parent 132d4f0 commit f32c4d4

File tree

18 files changed

+271
-113
lines changed

18 files changed

+271
-113
lines changed

app/src/main/java/dev/arkbuilders/arkmemo/contracts/PermissionContract.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ class PermissionContract : ActivityResultContract<String, Boolean>() {
1414
override fun createIntent(
1515
context: Context,
1616
input: String,
17-
) = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, Uri.parse(input))
17+
) = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, Uri.parse(input)).apply {
18+
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
19+
}
1820

1921
@RequiresApi(Build.VERSION_CODES.R)
2022
override fun parseResult(
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package dev.arkbuilders.arkmemo.models
2+
3+
enum class NoteType {
4+
TEXT,
5+
VOICE,
6+
GRAPHIC,
7+
}

app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepo.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.arkbuilders.arkmemo.repo
22

3+
import dev.arkbuilders.arklib.ResourceId
34
import dev.arkbuilders.arkmemo.models.SaveNoteResult
45

56
interface NotesRepo<Note> {
@@ -15,4 +16,6 @@ interface NotesRepo<Note> {
1516
suspend fun delete(note: Note)
1617

1718
suspend fun delete(notes: List<Note>)
19+
20+
suspend fun findNote(id: ResourceId): Note?
1821
}

app/src/main/java/dev/arkbuilders/arkmemo/repo/graphics/GraphicNotesRepo.kt

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import android.graphics.Canvas
88
import android.os.Environment
99
import android.util.Log
1010
import dagger.hilt.android.qualifiers.ApplicationContext
11+
import dev.arkbuilders.arklib.ResourceId
1112
import dev.arkbuilders.arklib.computeId
1213
import dev.arkbuilders.arklib.data.index.Resource
1314
import dev.arkbuilders.arkmemo.R
@@ -116,33 +117,20 @@ class GraphicNotesRepo
116117
private suspend fun readStorage() =
117118
withContext(iODispatcher) {
118119
root.listFiles(SVG_EXT) { path ->
119-
val svg = SVG.parse(path)
120-
if (svg == null) {
121-
Log.w(GRAPHICS_REPO, "Skipping invalid SVG: " + path)
122-
}
123-
val size = path.fileSize()
124-
val id = computeId(size, path)
125-
val resource =
126-
Resource(
127-
id = id,
128-
name = path.fileName.name,
129-
extension = path.extension,
130-
modified = path.getLastModifiedTime(),
131-
)
132-
133-
val userNoteProperties = helper.readProperties(id, "")
134-
val bitmap = exportBitmapFromSvg(fileName = id.toString(), svg = svg)
135-
136-
GraphicNote(
137-
title = userNoteProperties.title,
138-
description = userNoteProperties.description,
139-
svg = svg,
140-
resource = resource,
141-
thumb = bitmap,
142-
)
120+
path.toGraphicNote(helper = helper)
143121
}.filter { graphicNote -> graphicNote.svg != null }
144122
}
145123

124+
override suspend fun findNote(id: ResourceId): GraphicNote? {
125+
return withContext(iODispatcher) {
126+
root.listFiles(SVG_EXT) { path ->
127+
path.toGraphicNote(helper = helper)
128+
}.firstOrNull { graphicNote ->
129+
graphicNote.svg != null && id == graphicNote.resource?.id
130+
}
131+
}
132+
}
133+
146134
private fun exportBitmapFromSvg(
147135
fileName: String,
148136
svg: SVG?,
@@ -204,6 +192,33 @@ class GraphicNotesRepo
204192
return null
205193
}
206194
}
195+
196+
private fun Path.toGraphicNote(helper: NotesRepoHelper): GraphicNote {
197+
val svg = SVG.parse(this)
198+
if (svg == null) {
199+
Log.w(GRAPHICS_REPO, "Skipping invalid SVG: " + this)
200+
}
201+
val size = this.fileSize()
202+
val id = computeId(size, this)
203+
val resource =
204+
Resource(
205+
id = id,
206+
name = this.fileName.name,
207+
extension = this.extension,
208+
modified = this.getLastModifiedTime(),
209+
)
210+
211+
val userNoteProperties = helper.readProperties(id, "")
212+
val bitmap = exportBitmapFromSvg(fileName = id.toString(), svg = svg)
213+
214+
return GraphicNote(
215+
title = userNoteProperties.title,
216+
description = userNoteProperties.description,
217+
svg = svg,
218+
resource = resource,
219+
thumb = bitmap,
220+
)
221+
}
207222
}
208223

209224
private const val GRAPHICS_REPO = "GraphicNotesRepo"

app/src/main/java/dev/arkbuilders/arkmemo/repo/text/TextNotesRepo.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dev.arkbuilders.arkmemo.repo.text
22

33
import android.util.Log
4+
import dev.arkbuilders.arklib.ResourceId
45
import dev.arkbuilders.arklib.computeId
56
import dev.arkbuilders.arklib.data.index.Resource
67
import dev.arkbuilders.arkmemo.di.IO_DISPATCHER
@@ -56,6 +57,10 @@ class TextNotesRepo
5657
readStorage()
5758
}
5859

60+
override suspend fun findNote(id: ResourceId): TextNote? {
61+
return null
62+
}
63+
5964
private suspend fun write(
6065
note: TextNote,
6166
callback: (SaveNoteResult) -> Unit,

app/src/main/java/dev/arkbuilders/arkmemo/repo/voices/VoiceNotesRepo.kt

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dev.arkbuilders.arkmemo.repo.voices
22

33
import android.util.Log
4+
import dev.arkbuilders.arklib.ResourceId
45
import dev.arkbuilders.arklib.computeId
56
import dev.arkbuilders.arklib.data.index.Resource
67
import dev.arkbuilders.arkmemo.di.IO_DISPATCHER
@@ -39,6 +40,16 @@ class VoiceNotesRepo
3940
readStorage()
4041
}
4142

43+
override suspend fun findNote(id: ResourceId): VoiceNote? {
44+
return withContext(iODispatcher) {
45+
root.listFiles(VOICE_EXT) { path ->
46+
path.toVoiceNote(helper = helper)
47+
}.firstOrNull { voiceNote ->
48+
voiceNote.duration.isNotEmpty() && id == voiceNote.resource?.id
49+
}
50+
}
51+
}
52+
4253
override suspend fun delete(notes: List<VoiceNote>) {
4354
helper.deleteNotes(notes)
4455
}
@@ -101,25 +112,29 @@ class VoiceNotesRepo
101112
private suspend fun readStorage(): List<VoiceNote> =
102113
withContext(iODispatcher) {
103114
root.listFiles(VOICE_EXT) { path ->
104-
val id = computeId(path.fileSize(), path)
105-
val resource =
106-
Resource(
107-
id = id,
108-
name = path.name,
109-
extension = path.extension,
110-
modified = path.getLastModifiedTime(),
111-
)
112-
113-
val userNoteProperties = helper.readProperties(id, "")
114-
VoiceNote(
115-
title = userNoteProperties.title,
116-
description = userNoteProperties.description,
117-
path = path,
118-
duration = extractDuration(path.pathString),
119-
resource = resource,
120-
)
115+
path.toVoiceNote(helper)
121116
}.filter { voiceNote -> voiceNote.duration.isNotEmpty() }
122117
}
118+
119+
private fun Path.toVoiceNote(helper: NotesRepoHelper): VoiceNote {
120+
val id = computeId(this.fileSize(), this)
121+
val resource =
122+
Resource(
123+
id = id,
124+
name = this.name,
125+
extension = this.extension,
126+
modified = this.getLastModifiedTime(),
127+
)
128+
129+
val userNoteProperties = helper.readProperties(id, "")
130+
return VoiceNote(
131+
title = userNoteProperties.title,
132+
description = userNoteProperties.description,
133+
path = this,
134+
duration = extractDuration(this.pathString),
135+
resource = resource,
136+
)
137+
}
123138
}
124139

125140
private const val VOICES_REPO = "VoiceNotesRepo"

app/src/main/java/dev/arkbuilders/arkmemo/ui/activities/MainActivity.kt

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package dev.arkbuilders.arkmemo.ui.activities
33
import android.content.Intent
44
import android.os.Bundle
55
import android.view.WindowManager
6-
import androidx.activity.result.contract.ActivityResultContracts
76
import androidx.annotation.IdRes
87
import androidx.appcompat.app.AppCompatActivity
98
import androidx.core.content.ContextCompat
@@ -13,7 +12,6 @@ import androidx.fragment.app.Fragment
1312
import by.kirich1409.viewbindingdelegate.viewBinding
1413
import dagger.hilt.android.AndroidEntryPoint
1514
import dev.arkbuilders.arkmemo.R
16-
import dev.arkbuilders.arkmemo.contracts.PermissionContract
1715
import dev.arkbuilders.arkmemo.databinding.ActivityMainBinding
1816
import dev.arkbuilders.arkmemo.models.RootNotFound
1917
import dev.arkbuilders.arkmemo.preferences.MemoPreferences
@@ -22,6 +20,7 @@ import dev.arkbuilders.arkmemo.ui.dialogs.FilePickerDialog
2220
import dev.arkbuilders.arkmemo.ui.fragments.BaseFragment
2321
import dev.arkbuilders.arkmemo.ui.fragments.EditTextNotesFragment
2422
import dev.arkbuilders.arkmemo.ui.fragments.NotesFragment
23+
import dev.arkbuilders.arkmemo.utils.PermissionManager
2524
import dev.arkbuilders.components.filepicker.onArkPathPicked
2625
import javax.inject.Inject
2726
import kotlin.io.path.exists
@@ -37,25 +36,10 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
3736
private val fragContainer = R.id.container
3837

3938
var fragment: Fragment = NotesFragment()
39+
val permissionManager = PermissionManager(activity = this)
4040

4141
init {
42-
FilePickerDialog.readPermLauncher =
43-
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
44-
if (isGranted) {
45-
FilePickerDialog.show()
46-
} else {
47-
finish()
48-
}
49-
}
50-
51-
FilePickerDialog.readPermLauncherSdkR =
52-
registerForActivityResult(PermissionContract()) { isGranted ->
53-
if (isGranted) {
54-
FilePickerDialog.show()
55-
} else {
56-
finish()
57-
}
58-
}
42+
FilePickerDialog.permissionManager = permissionManager
5943
}
6044

6145
override fun onCreate(savedInstanceState: Bundle?) {
@@ -72,21 +56,27 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
7256
showFragment(savedInstanceState)
7357
}
7458

75-
val storageFolderExisting = memoPreferences.getNotesStorage().exists()
76-
if (memoPreferences.storageNotAvailable()) {
77-
if (!storageFolderExisting) {
78-
showNoNoteStorageDialog(RootNotFound(rootPath = memoPreferences.getPath()))
79-
} else {
80-
FilePickerDialog.show(this, supportFragmentManager)
81-
}
82-
} else {
83-
if (memoPreferences.isLastLaunchSuccess()) {
84-
showFragment(savedInstanceState)
59+
permissionManager.askForWriteStorage { granted ->
60+
if (!granted) {
61+
finish()
8562
} else {
86-
showRetrySelectRootDialog(
87-
rootPath = memoPreferences.getPath(),
88-
savedInstanceState = savedInstanceState,
89-
)
63+
val storageFolderExisting = memoPreferences.getNotesStorage().exists()
64+
if (memoPreferences.storageNotAvailable()) {
65+
if (!storageFolderExisting) {
66+
showNoNoteStorageDialog(RootNotFound(rootPath = memoPreferences.getPath()))
67+
} else {
68+
FilePickerDialog.show(this, supportFragmentManager)
69+
}
70+
} else {
71+
if (memoPreferences.isLastLaunchSuccess()) {
72+
showFragment(savedInstanceState)
73+
} else {
74+
showRetrySelectRootDialog(
75+
rootPath = memoPreferences.getPath(),
76+
savedInstanceState = savedInstanceState,
77+
)
78+
}
79+
}
9080
}
9181
}
9282
}

app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/FilePickerDialog.kt

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
package dev.arkbuilders.arkmemo.ui.dialogs
22

3-
import android.Manifest
4-
import android.content.pm.PackageManager
5-
import android.os.Build
63
import android.os.Bundle
7-
import android.os.Environment
8-
import androidx.activity.result.ActivityResultLauncher
94
import androidx.appcompat.app.AppCompatActivity
10-
import androidx.core.content.ContextCompat
115
import androidx.fragment.app.FragmentManager
126
import dagger.hilt.android.AndroidEntryPoint
13-
import dev.arkbuilders.arkmemo.BuildConfig
147
import dev.arkbuilders.arkmemo.R
158
import dev.arkbuilders.arkmemo.preferences.MemoPreferences
9+
import dev.arkbuilders.arkmemo.utils.Permission
10+
import dev.arkbuilders.arkmemo.utils.PermissionManager
1611
import dev.arkbuilders.components.filepicker.ArkFilePickerConfig
1712
import dev.arkbuilders.components.filepicker.ArkFilePickerFragment
1813
import dev.arkbuilders.components.filepicker.ArkFilePickerMode
@@ -39,8 +34,7 @@ class FilePickerDialog : ArkFilePickerFragment() {
3934
companion object {
4035
private const val TAG = "file_picker"
4136
private lateinit var fragmentManager: FragmentManager
42-
var readPermLauncher: ActivityResultLauncher<String>? = null
43-
var readPermLauncherSdkR: ActivityResultLauncher<String>? = null
37+
var permissionManager: PermissionManager? = null
4438

4539
fun show() {
4640
newInstance(getFilePickerConfig()).show(fragmentManager, TAG)
@@ -51,10 +45,16 @@ class FilePickerDialog : ArkFilePickerFragment() {
5145
fragmentManager: FragmentManager,
5246
) {
5347
Companion.fragmentManager = fragmentManager
54-
if (isReadPermissionGranted(activity)) {
48+
if (Permission.hasStoragePermission(activity)) {
5549
show()
5650
} else {
57-
askForReadPermissions()
51+
permissionManager?.askForWriteStorage { granted ->
52+
if (granted) {
53+
show()
54+
} else {
55+
activity.finish()
56+
}
57+
}
5858
}
5959
}
6060

@@ -63,24 +63,6 @@ class FilePickerDialog : ArkFilePickerFragment() {
6363
setConfig(config)
6464
}
6565

66-
private fun isReadPermissionGranted(activity: AppCompatActivity): Boolean {
67-
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
68-
Environment.isExternalStorageManager()
69-
} else {
70-
ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) ==
71-
PackageManager.PERMISSION_GRANTED
72-
}
73-
}
74-
75-
private fun askForReadPermissions() {
76-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
77-
val packageUri = "package:" + BuildConfig.APPLICATION_ID
78-
readPermLauncherSdkR?.launch(packageUri)
79-
} else {
80-
readPermLauncher?.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
81-
}
82-
}
83-
8466
private fun getFilePickerConfig() =
8567
ArkFilePickerConfig(
8668
mode = ArkFilePickerMode.FOLDER,

app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkMediaPlayerFragment.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ class ArkMediaPlayerFragment : BaseEditNoteFragment() {
5959
return false
6060
}
6161

62+
override fun onViewRestoredWithNote(note: Note) {
63+
}
64+
6265
private fun initUI() {
6366
binding.toolbar.ivRightActionIcon.setOnClickListener {
6467
showDeleteNoteDialog(note)

0 commit comments

Comments
 (0)