feat: ask for all files access and offer storage and Bluetooth permissions in Setup

This commit is contained in:
xXJsonDeruloXx committed 2026-10-11 10:08:10 -04:00
1 parent abfcb338c4
commit eb84def168
9 files changed
+112 -2

No files matched your search

+1
View File
@@ -7,6 +7,7 @@
<!-- Session logs go to Downloads so a failed run can be handed over as one folder. -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- Store download progress and "installed" / "failed" notices (Android 13+). -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
@@ -182,6 +182,7 @@ class MainActivity : ComponentActivity() {
private val wifiLocationReceiver = object : android.content.BroadcastReceiver() {
override fun onReceive(context: android.content.Context?, intent: Intent?) {
refreshWifiDiscovery()
refreshPermissionRows()
}
}
private val mediaRefresh = Runnable {
@@ -370,6 +371,8 @@ class MainActivity : ComponentActivity() {
private var runningLabel by mutableStateOf<String?>(null)
private var logsEnabled by mutableStateOf(true)
private var agentCommands by mutableStateOf(false)
private var bluetoothAllowed by mutableStateOf<Boolean?>(null)
private var allFilesAllowed by mutableStateOf<Boolean?>(null)
private var showRoms by mutableStateOf(false)
private var homeAppSelected by mutableStateOf(false)
private var homeScreenEnabled by mutableStateOf(false)
@@ -569,6 +572,7 @@ class MainActivity : ComponentActivity() {
desktopInstalled = desktopInstalled, desktopPresent = desktopPresent,
offlineAccount = offlineAccount, offline = offline,
frameGenLabel = frameGenLabel, romsDir = romsDir, logsEnabled = logsEnabled, agentCommands = agentCommands,
bluetoothAllowed = bluetoothAllowed, allFilesAllowed = allFilesAllowed,
steamGames = steamGames, emulators = emulatorList, running = runningLabel,
frameGen = FrameGen.mode(this),
lossless = lossless,
@@ -716,6 +720,8 @@ class MainActivity : ComponentActivity() {
com.droiddeck.launcher.agent.AgentAccess.setCommandsEnabled(this, !com.droiddeck.launcher.agent.AgentAccess.commandsEnabled(this))
agentCommands = com.droiddeck.launcher.agent.AgentAccess.commandsEnabled(this)
},
onBluetoothAllowed = { askBluetooth() },
onAllFilesAllowed = { com.droiddeck.launcher.core.AllFilesAccess.openSettings(this) },
onShareLogs = {
SessionLogShare.prepare(this, { SessionLogShare.latest(this) }) { zip ->
if (zip == null) android.widget.Toast.makeText(this, R.string.main_no_logs_yet, android.widget.Toast.LENGTH_LONG).show()
@@ -913,6 +919,14 @@ class MainActivity : ComponentActivity() {
SessionPrefs.setMicAsked(this)
}
if (wanted.isNotEmpty()) requestPermissions(wanted.toTypedArray(), 1)
else if (!com.droiddeck.launcher.core.AllFilesAccess.granted() && !SessionPrefs.allFilesAsked(this)) {
Thread({
if (com.droiddeck.launcher.core.AllFilesAccess.usesSharedStorage(this)) runOnUiThread {
SessionPrefs.setAllFilesAsked(this)
com.droiddeck.launcher.core.AllFilesAccess.ask(this)
}
}, "all-files-access").start()
}
// A store sign-in an earlier build left in plain text is sealed with the Keystore key.
Thread({ com.droiddeck.launcher.stores.StoreAccounts.encryptAll(this) }, "store-credentials").start()
// A session folder left without its ending - the process was killed - gets it now.
@@ -993,6 +1007,26 @@ class MainActivity : ComponentActivity() {
}
}
private fun refreshPermissionRows() {
bluetoothAllowed = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED else null
allFilesAllowed = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) com.droiddeck.launcher.core.AllFilesAccess.granted() else null
}
private fun askBluetooth() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return
val permission = Manifest.permission.BLUETOOTH_CONNECT
val granted = checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
if (granted || (SessionPrefs.bluetoothAsked(this) && !shouldShowRequestPermissionRationale(permission))) {
runCatching {
startActivity(Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, android.net.Uri.parse("package:$packageName")))
}
} else {
SessionPrefs.setBluetoothAsked(this)
requestPermissions(arrayOf(permission), 3)
}
}
override fun onResume() {
super.onResume()
orientation = SessionPrefs.orientation(this)
@@ -0,0 +1,45 @@
package com.droiddeck.launcher.core
import android.app.Activity
import android.app.AlertDialog
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.Settings
import com.droiddeck.launcher.R
import com.droiddeck.launcher.frontend.AddedExes
import com.droiddeck.launcher.stores.StoreInstallRoot
object AllFilesAccess {
fun granted(): Boolean = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || Environment.isExternalStorageManager()
fun usesSharedStorage(context: Context): Boolean {
val own = (listOf(context.filesDir.parentFile) + context.getExternalFilesDirs(null).map { it?.parentFile })
.filterNotNull().map { it.absolutePath + "/" }
fun shared(path: String) = own.none { (path + "/").startsWith(it) }
return AddedExes.list(context).any { shared(it.folder) } ||
StoreInstallRoot.roots(context).any { shared(it.absolutePath) }
}
fun ask(activity: Activity) {
if (granted() || activity.isFinishing) return
AlertDialog.Builder(activity)
.setTitle(R.string.all_files_title)
.setMessage(R.string.all_files_message)
.setPositiveButton(R.string.all_files_allow) { _, _ -> openSettings(activity) }
.setNegativeButton(R.string.all_files_later, null)
.show()
}
fun openSettings(activity: Activity) {
val forApp = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, Uri.parse("package:" + activity.packageName))
try {
activity.startActivity(forApp)
} catch (e: ActivityNotFoundException) {
runCatching { activity.startActivity(Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)) }
}
}
}
@@ -2,6 +2,7 @@ package com.droiddeck.launcher.files
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@@ -23,7 +24,9 @@ class FileManagerActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestedOrientation = com.droiddeck.launcher.session.SessionPrefs.orientation(this)
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
com.droiddeck.launcher.core.AllFilesAccess.ask(this)
} else if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 1)
}
val initialDir = intent.getStringExtra(EXTRA_START_DIR)?.let(::File)
@@ -4,6 +4,7 @@ import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@@ -36,7 +37,9 @@ class FilePickerActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestedOrientation = com.droiddeck.launcher.session.SessionPrefs.orientation(this)
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
com.droiddeck.launcher.core.AllFilesAccess.ask(this)
} else if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 1)
}
@@ -259,6 +259,12 @@ object SessionPrefs {
prefs(context).edit().putBoolean("micAsked", true).apply()
}
fun allFilesAsked(context: Context): Boolean = prefs(context).getBoolean("allFilesAsked", false)
fun setAllFilesAsked(context: Context) {
prefs(context).edit().putBoolean("allFilesAsked", true).apply()
}
fun bluetoothAsked(context: Context): Boolean = prefs(context).getBoolean("bluetoothAsked", false)
fun setBluetoothAsked(context: Context) {
@@ -97,6 +97,8 @@ class FrontEndState(
val logsEnabled: Boolean,
/** Setup's Debugging tools toggle (AgentAccess): off unless the user turns it on. */
val agentCommands: Boolean = false,
val bluetoothAllowed: Boolean? = null,
val allFilesAllowed: Boolean? = null,
val steamGames: List<Library.SteamGame>,
val emulators: List<Library.Emulator>,
val running: String?,
@@ -198,6 +200,8 @@ class FrontEndActions(
val onBrowseFiles: (File) -> Unit = {},
val onLogs: () -> Unit,
val onAgentCommands: () -> Unit = {},
val onBluetoothAllowed: () -> Unit = {},
val onAllFilesAllowed: () -> Unit = {},
val onShareLogs: () -> Unit = {},
val onClearLogs: () -> Unit = {},
val onOffline: () -> Unit,
@@ -311,6 +311,12 @@ internal fun SetupPanel(
}
ToggleRow(host, "logs", stringResource(R.string.setup_logs), stringResource(R.string.setup_logs_hint), s.logsEnabled) { a.onLogs() }
ToggleRow(host, "agent-commands", stringResource(R.string.setup_agent_commands), null, s.agentCommands) { a.onAgentCommands() }
s.allFilesAllowed?.let { on ->
ToggleRow(host, "all-files", stringResource(R.string.setup_all_files), stringResource(R.string.setup_all_files_hint), on) { a.onAllFilesAllowed() }
}
s.bluetoothAllowed?.let { on ->
ToggleRow(host, "bluetooth", stringResource(R.string.setup_bluetooth), stringResource(R.string.setup_bluetooth_hint), on) { a.onBluetoothAllowed() }
}
ActionRow(stringResource(R.string.setup_latest_logs), stringResource(R.string.drawer_logs_hint), stringResource(R.string.drawer_share_logs), a.onShareLogs,
progress = com.droiddeck.launcher.session.SessionLogShare.progress)
ActionRow(stringResource(R.string.setup_saved_logs), stringResource(R.string.setup_saved_logs_hint, com.droiddeck.launcher.session.SessionPaths.KEEP_SESSIONS), stringResource(R.string.setup_clear_logs), a.onClearLogs)
+8
View File
@@ -1727,6 +1727,14 @@
<!-- Label of the notification's reply field -->
<string name="adbpair_code_label">6-digit pairing code</string>
<string name="adbpair_code_hint">Tap Enter code and type the 6 digits shown in Settings.</string>
<string name="setup_all_files">All files access</string>
<string name="setup_all_files_hint">Lets DroidDeck see games and programs on your storage and SD card</string>
<string name="setup_bluetooth">Bluetooth Steam Controller</string>
<string name="setup_bluetooth_hint">Lets DroidDeck find a paired Steam Controller (Nearby devices)</string>
<string name="all_files_title">Allow access to your game folders</string>
<string name="all_files_message">DroidDeck runs games and programs from your phone\'s storage and SD card. Android only lets it see those files with All files access, which you can turn on for DroidDeck on the next screen.</string>
<string name="all_files_allow">Open settings</string>
<string name="all_files_later">Not now</string>
<string name="adbpair_title_code">Enter the Wi-Fi pairing code</string>
<string name="adbpair_title_retry">Try the code again</string>
<!-- Notification action button -->