Skip to content

Commit bfc1ce4

Browse files
committed
storage: scope legacy permissions to advanced import
1 parent 4e40abb commit bfc1ce4

4 files changed

Lines changed: 71 additions & 5 deletions

File tree

app/src/main/java/com/kyhsgeekcode/disassembler/importing/ImportEntryPointCatalog.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.kyhsgeekcode.disassembler.importing
22

3+
import com.kyhsgeekcode.disassembler.storagePermissionsForSdk
34
import com.kyhsgeekcode.disassembler.R
45

56
sealed class ImportEntryPoint(val labelRes: Int) {
@@ -20,3 +21,10 @@ object DefaultImportEntryPointCatalog : ImportEntryPointCatalog {
2021
}
2122
}
2223
}
24+
25+
fun legacyPermissionsForImportEntryPoint(entryPoint: ImportEntryPoint, sdkInt: Int): Array<String> {
26+
return when (entryPoint) {
27+
ImportEntryPoint.SafImport -> emptyArray()
28+
ImportEntryPoint.AdvancedImport -> storagePermissionsForSdk(sdkInt)
29+
}
30+
}

app/src/main/java/com/kyhsgeekcode/disassembler/ui/MainTab.kt

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package com.kyhsgeekcode.disassembler.ui
22

33
import android.app.Activity
44
import android.content.Intent
5+
import android.content.pm.PackageManager
6+
import android.os.Build
57
import android.widget.Toast
68
import androidx.activity.compose.ManagedActivityResultLauncher
79
import androidx.activity.compose.rememberLauncherForActivityResult
810
import androidx.activity.result.contract.ActivityResultContracts
11+
import androidx.core.content.ContextCompat
912
import androidx.compose.foundation.layout.Arrangement
1013
import androidx.compose.foundation.layout.Column
1114
import androidx.compose.foundation.layout.Row
@@ -33,6 +36,7 @@ import com.kyhsgeekcode.disassembler.R
3336
import com.kyhsgeekcode.disassembler.exporting.buildProjectExportFileName
3437
import com.kyhsgeekcode.disassembler.importing.DefaultImportEntryPointCatalog
3538
import com.kyhsgeekcode.disassembler.importing.ImportEntryPoint
39+
import com.kyhsgeekcode.disassembler.importing.legacyPermissionsForImportEntryPoint
3640
import com.kyhsgeekcode.disassembler.preference.PowerUserModeSettings
3741
import com.kyhsgeekcode.disassembler.viewmodel.MainViewModel
3842
import com.kyhsgeekcode.filechooser.NewFileChooserActivity
@@ -58,6 +62,15 @@ fun ProjectOverview(viewModel: MainViewModel) {
5862
}
5963
}
6064
}
65+
val legacyPermissionLauncher = rememberLauncherForActivityResult(
66+
ActivityResultContracts.RequestMultiplePermissions()
67+
) { grantResults ->
68+
if (grantResults.values.all { it }) {
69+
launchAdvancedImportActivity(context, advancedImportLauncher)
70+
} else {
71+
Toast.makeText(context, R.string.permission_needed, Toast.LENGTH_LONG).show()
72+
}
73+
}
6174
val safImportLauncher = rememberLauncherForActivityResult(
6275
ActivityResultContracts.OpenDocument()
6376
) { selectedUri ->
@@ -131,7 +144,8 @@ fun ProjectOverview(viewModel: MainViewModel) {
131144
entryPoint,
132145
context = context,
133146
safImportLauncher = safImportLauncher,
134-
advancedImportLauncher = advancedImportLauncher
147+
advancedImportLauncher = advancedImportLauncher,
148+
legacyPermissionLauncher = legacyPermissionLauncher
135149
)
136150
}
137151
) {
@@ -198,15 +212,38 @@ private fun launchImportEntryPoint(
198212
entryPoint: ImportEntryPoint,
199213
context: android.content.Context,
200214
safImportLauncher: ManagedActivityResultLauncher<Array<String>, android.net.Uri?>,
201-
advancedImportLauncher: ManagedActivityResultLauncher<Intent, androidx.activity.result.ActivityResult>
215+
advancedImportLauncher: ManagedActivityResultLauncher<Intent, androidx.activity.result.ActivityResult>,
216+
legacyPermissionLauncher: ManagedActivityResultLauncher<Array<String>, Map<String, @JvmSuppressWildcards Boolean>>
202217
) {
203218
when (entryPoint) {
204219
ImportEntryPoint.SafImport -> safImportLauncher.launch(arrayOf("*/*"))
205220
ImportEntryPoint.AdvancedImport -> {
206-
val intent = Intent(context, NewFileChooserActivity::class.java).apply {
207-
putExtra(NewFileChooserActivity.EXTRA_POWER_USER_MODE, true)
221+
val requiredPermissions =
222+
legacyPermissionsForImportEntryPoint(entryPoint, Build.VERSION.SDK_INT)
223+
if (requiredPermissions.isNotEmpty() && !hasAllPermissions(context, requiredPermissions)) {
224+
legacyPermissionLauncher.launch(requiredPermissions)
225+
} else {
226+
launchAdvancedImportActivity(context, advancedImportLauncher)
208227
}
209-
advancedImportLauncher.launch(intent)
210228
}
211229
}
212230
}
231+
232+
private fun launchAdvancedImportActivity(
233+
context: android.content.Context,
234+
advancedImportLauncher: ManagedActivityResultLauncher<Intent, androidx.activity.result.ActivityResult>
235+
) {
236+
val intent = Intent(context, NewFileChooserActivity::class.java).apply {
237+
putExtra(NewFileChooserActivity.EXTRA_POWER_USER_MODE, true)
238+
}
239+
advancedImportLauncher.launch(intent)
240+
}
241+
242+
private fun hasAllPermissions(
243+
context: android.content.Context,
244+
permissions: Array<String>
245+
): Boolean {
246+
return permissions.all {
247+
ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
248+
}
249+
}

app/src/test/java/com/kyhsgeekcode/disassembler/importing/ImportEntryPointCatalogTest.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.kyhsgeekcode.disassembler.importing
22

3+
import android.Manifest
34
import org.junit.jupiter.api.Assertions.assertEquals
45
import org.junit.jupiter.api.Test
56

@@ -19,4 +20,23 @@ class ImportEntryPointCatalogTest {
1920
DefaultImportEntryPointCatalog.visibleEntryPoints(powerUserMode = true)
2021
)
2122
}
23+
24+
@Test
25+
fun `SAF import requires no legacy storage permissions on any supported SDK`() {
26+
assertEquals(emptyArray<String>().toList(), legacyPermissionsForImportEntryPoint(ImportEntryPoint.SafImport, 28).toList())
27+
assertEquals(emptyArray<String>().toList(), legacyPermissionsForImportEntryPoint(ImportEntryPoint.SafImport, 35).toList())
28+
}
29+
30+
@Test
31+
fun `advanced import requires legacy storage permissions only on Android 9 and below`() {
32+
assertEquals(
33+
listOf(
34+
Manifest.permission.READ_EXTERNAL_STORAGE,
35+
Manifest.permission.WRITE_EXTERNAL_STORAGE
36+
),
37+
legacyPermissionsForImportEntryPoint(ImportEntryPoint.AdvancedImport, 28).toList()
38+
)
39+
assertEquals(emptyArray<String>().toList(), legacyPermissionsForImportEntryPoint(ImportEntryPoint.AdvancedImport, 29).toList())
40+
assertEquals(emptyArray<String>().toList(), legacyPermissionsForImportEntryPoint(ImportEntryPoint.AdvancedImport, 35).toList())
41+
}
2242
}

docs/maintenance/implementation-log.ko.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
| 이슈 `#719` 최신판 체험 경로 부족 | rolling `Preview latest` prerelease를 발행하는 `preview.yml`을 추가하고 README에 preview/debug 배포 정책을 문서화 | CI artifact만 아는 사람만 최신판을 받던 상태에서, GitHub Releases의 prerelease 자산으로도 최신 debug 빌드를 쉽게 배포할 수 있게 했다 | `.github/workflows/preview.yml`, `README.md` | 완료 |
1616
| 저장소 정책 현대화 착수, 이슈 `#95` 대응 기반 | SAF 기반 선택 경로를 도입하고 `content://` 입력을 앱 내부 import 파일로 저장 | 외부 절대경로 전제를 줄이고, 재시작 이후에도 다시 열 수 있는 URI 권한 흐름을 시작했다 | `app/src/main/java/com/kyhsgeekcode/filechooser/NewFileChooserActivity.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/viewmodel/MainViewModel.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/PermissionUtils.kt` | 진행 중 |
1717
| 이슈 `#95` 외부 문서 인텐트 권한 누락 | 앱이 `ACTION_VIEW``EXTRA_STREAM`으로 열린 경우에도 persistable grant 가능 여부를 계산하고 `content://` URI 권한을 선제적으로 유지 | SAF picker 밖에서 들어온 문서도 같은 storage 정책 흐름으로 흡수해서, provider가 허용하는 경우 앱 재실행 이후에도 접근이 끊길 가능성을 줄였다 | `app/src/main/java/com/kyhsgeekcode/disassembler/MainActivity.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/PermissionUtils.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/PermissionUtilsTest.kt` | 완료 |
18+
| 이슈 `#95` legacy 저장소 권한 범위 축소 | import entry-point별 legacy 권한 요구를 분리하고, `Advanced import`만 Android 9 이하에서 권한 요청을 하도록 변경 | 기본 SAF import는 어떤 지원 SDK에서도 저장소 권한을 요구하지 않게 고정하고, 구형 Android의 raw filesystem 진입점에서만 legacy 권한 모델을 제한적으로 유지했다 | `app/src/main/java/com/kyhsgeekcode/disassembler/importing/ImportEntryPointCatalog.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/ui/MainTab.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/importing/ImportEntryPointCatalogTest.kt` | 완료 |
1819
| 프로젝트 경계의 raw path 의존 | `ProjectModel`에 source helper를 추가하고 프로젝트 트리/리스트/저장소가 helper를 사용하도록 교체 | `sourceFilePath` 문자열 조합을 한곳으로 모아서 이후 `sourceDescriptor` 중심 구조로 더 옮기기 쉬운 상태를 만들었다 | `app/src/main/java/com/kyhsgeekcode/disassembler/project/models/ProjectModel.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/ui/FileDrawerTree.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/FileDrawerListAdapter.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/FileDrawerListItem.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/project/ProjectDataStorage.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/viewmodel/MainViewModel.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/ProjectManagerTest.kt` | 완료 |
1920
| broad storage 우회 경로 정리 | 더 이상 쓰지 않는 legacy picker 코드와 Android 11+의 `MANAGE_ALL_FILES_ACCESS` 유도 설정을 제거 | SAF 기반 경로를 기본 흐름으로 고정하고, 정책상 불리한 all-files access 진입점을 줄였다 | `app/src/main/java/com/kyhsgeekcode/disassembler/MainActivity.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/preference/SettingsFragment.kt`, `app/src/main/res/xml/pref_settings.xml`, `app/src/main/res/xml-v30/pref_settings.xml`, `app/src/main/res/values/array.xml` | 완료 |
2021
| 프로젝트 저장 모델 확장 | `sourceDescriptor`를 추가하고 새 프로젝트 생성 시 원본 유형을 함께 기록 | 기존 `sourceFilePath` 호환성은 유지하면서 이후 `Uri / file path / cache` 구분으로 넘어갈 발판을 만들었다 | `app/src/main/java/com/kyhsgeekcode/disassembler/project/models/ProjectModel.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/project/ProjectManager.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/viewmodel/MainViewModel.kt` | 완료 |

0 commit comments

Comments
 (0)