Skip to content

Commit a7069ce

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 e353a22 commit a7069ce

File tree

18 files changed

+271
-115
lines changed

18 files changed

+271
-115
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
@@ -118,33 +119,20 @@ class GraphicNotesRepo
118119
withContext(iODispatcher) {
119120
Log.d(GRAPHICS_REPO, "readStorage")
120121
root.listFiles(SVG_EXT) { path ->
121-
val svg = SVG.parse(path)
122-
if (svg == null) {
123-
Log.w(GRAPHICS_REPO, "Skipping invalid SVG: " + path)
124-
}
125-
val size = path.fileSize()
126-
val id = computeId(size, path)
127-
val resource =
128-
Resource(
129-
id = id,
130-
name = path.fileName.name,
131-
extension = path.extension,
132-
modified = path.getLastModifiedTime(),
133-
)
134-
135-
val userNoteProperties = helper.readProperties(id, "")
136-
val bitmap = exportBitmapFromSvg(fileName = id.toString(), svg = svg)
137-
138-
GraphicNote(
139-
title = userNoteProperties.title,
140-
description = userNoteProperties.description,
141-
svg = svg,
142-
resource = resource,
143-
thumb = bitmap,
144-
)
122+
path.toGraphicNote(helper = helper)
145123
}.filter { graphicNote -> graphicNote.svg != null }
146124
}
147125

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

213228
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
}
@@ -103,25 +114,29 @@ class VoiceNotesRepo
103114
withContext(iODispatcher) {
104115
Log.d(VOICES_REPO, "readStorage")
105116
root.listFiles(VOICE_EXT) { path ->
106-
val id = computeId(path.fileSize(), path)
107-
val resource =
108-
Resource(
109-
id = id,
110-
name = path.name,
111-
extension = path.extension,
112-
modified = path.getLastModifiedTime(),
113-
)
114-
115-
val userNoteProperties = helper.readProperties(id, "")
116-
VoiceNote(
117-
title = userNoteProperties.title,
118-
description = userNoteProperties.description,
119-
path = path,
120-
duration = extractDuration(path.pathString),
121-
resource = resource,
122-
)
117+
path.toVoiceNote(helper)
123118
}.filter { voiceNote -> voiceNote.duration.isNotEmpty() }
124119
}
120+
121+
private fun Path.toVoiceNote(helper: NotesRepoHelper): VoiceNote {
122+
val id = computeId(this.fileSize(), this)
123+
val resource =
124+
Resource(
125+
id = id,
126+
name = this.name,
127+
extension = this.extension,
128+
modified = this.getLastModifiedTime(),
129+
)
130+
131+
val userNoteProperties = helper.readProperties(id, "")
132+
return VoiceNote(
133+
title = userNoteProperties.title,
134+
description = userNoteProperties.description,
135+
path = this,
136+
duration = extractDuration(this.pathString),
137+
resource = resource,
138+
)
139+
}
125140
}
126141

127142
private const val VOICES_REPO = "VoiceNotesRepo"

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

Lines changed: 23 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import android.content.Intent
44
import android.os.Bundle
55
import android.util.Log
66
import android.view.WindowManager
7-
import androidx.activity.result.contract.ActivityResultContracts
87
import androidx.annotation.IdRes
98
import androidx.appcompat.app.AppCompatActivity
109
import androidx.core.content.ContextCompat
@@ -14,7 +13,6 @@ import androidx.fragment.app.Fragment
1413
import by.kirich1409.viewbindingdelegate.viewBinding
1514
import dagger.hilt.android.AndroidEntryPoint
1615
import dev.arkbuilders.arkmemo.R
17-
import dev.arkbuilders.arkmemo.contracts.PermissionContract
1816
import dev.arkbuilders.arkmemo.databinding.ActivityMainBinding
1917
import dev.arkbuilders.arkmemo.models.RootNotFound
2018
import dev.arkbuilders.arkmemo.preferences.MemoPreferences
@@ -23,6 +21,7 @@ import dev.arkbuilders.arkmemo.ui.dialogs.FilePickerDialog
2321
import dev.arkbuilders.arkmemo.ui.fragments.BaseFragment
2422
import dev.arkbuilders.arkmemo.ui.fragments.EditTextNotesFragment
2523
import dev.arkbuilders.arkmemo.ui.fragments.NotesFragment
24+
import dev.arkbuilders.arkmemo.utils.PermissionManager
2625
import dev.arkbuilders.components.filepicker.onArkPathPicked
2726
import javax.inject.Inject
2827
import kotlin.io.path.exists
@@ -38,27 +37,10 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
3837
private val fragContainer = R.id.container
3938

4039
var fragment: Fragment = NotesFragment()
40+
val permissionManager = PermissionManager(activity = this)
4141

4242
init {
43-
FilePickerDialog.readPermLauncher =
44-
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
45-
Log.d(ACTIVITY_TAG, "readPermLauncher isGranted: $isGranted")
46-
if (isGranted) {
47-
FilePickerDialog.show()
48-
} else {
49-
finish()
50-
}
51-
}
52-
53-
FilePickerDialog.readPermLauncherSdkR =
54-
registerForActivityResult(PermissionContract()) { isGranted ->
55-
Log.d(ACTIVITY_TAG, "readPermLauncherSdkR isGranted: $isGranted")
56-
if (isGranted) {
57-
FilePickerDialog.show()
58-
} else {
59-
finish()
60-
}
61-
}
43+
FilePickerDialog.permissionManager = permissionManager
6244
}
6345

6446
override fun onCreate(savedInstanceState: Bundle?) {
@@ -76,21 +58,27 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
7658
showFragment(savedInstanceState)
7759
}
7860

79-
val storageFolderExisting = memoPreferences.getNotesStorage().exists()
80-
if (memoPreferences.storageNotAvailable()) {
81-
if (!storageFolderExisting) {
82-
showNoNoteStorageDialog(RootNotFound(rootPath = memoPreferences.getPath()))
83-
} else {
84-
FilePickerDialog.show(this, supportFragmentManager)
85-
}
86-
} else {
87-
if (memoPreferences.isLastLaunchSuccess()) {
88-
showFragment(savedInstanceState)
61+
permissionManager.askForWriteStorage { granted ->
62+
if (!granted) {
63+
finish()
8964
} else {
90-
showRetrySelectRootDialog(
91-
rootPath = memoPreferences.getPath(),
92-
savedInstanceState = savedInstanceState,
93-
)
65+
val storageFolderExisting = memoPreferences.getNotesStorage().exists()
66+
if (memoPreferences.storageNotAvailable()) {
67+
if (!storageFolderExisting) {
68+
showNoNoteStorageDialog(RootNotFound(rootPath = memoPreferences.getPath()))
69+
} else {
70+
FilePickerDialog.show(this, supportFragmentManager)
71+
}
72+
} else {
73+
if (memoPreferences.isLastLaunchSuccess()) {
74+
showFragment(savedInstanceState)
75+
} else {
76+
showRetrySelectRootDialog(
77+
rootPath = memoPreferences.getPath(),
78+
savedInstanceState = savedInstanceState,
79+
)
80+
}
81+
}
9482
}
9583
}
9684
}

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
@@ -60,6 +60,9 @@ class ArkMediaPlayerFragment : BaseEditNoteFragment() {
6060
return false
6161
}
6262

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

0 commit comments

Comments
 (0)