android: Add user directory shortcut

This commit is contained in:
Charles Lombardo 2023-04-30 22:14:57 -04:00 committed by bunnei
parent 265b9139e0
commit 912bf6a0c6
6 changed files with 140 additions and 25 deletions

View file

@ -15,6 +15,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application <application
android:name="org.yuzu.yuzu_emu.YuzuApplication" android:name="org.yuzu.yuzu_emu.YuzuApplication"

View file

@ -15,23 +15,29 @@ import java.io.File
fun Context.getPublicFilesDir() : File = getExternalFilesDir(null) ?: filesDir fun Context.getPublicFilesDir() : File = getExternalFilesDir(null) ?: filesDir
class YuzuApplication : Application() { class YuzuApplication : Application() {
private fun createNotificationChannel() { private fun createNotificationChannels() {
// Create the NotificationChannel, but only on API 26+ because val emulationChannel = NotificationChannel(
// the NotificationChannel class is new and not in the support library getString(R.string.emulation_notification_channel_id),
val name: CharSequence = getString(R.string.app_notification_channel_name) getString(R.string.emulation_notification_channel_name),
val description = getString(R.string.app_notification_channel_description)
val channel = NotificationChannel(
getString(R.string.app_notification_channel_id),
name,
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
) )
channel.description = description emulationChannel.description = getString(R.string.emulation_notification_channel_description)
channel.setSound(null, null) emulationChannel.setSound(null, null)
channel.vibrationPattern = null emulationChannel.vibrationPattern = null
val noticeChannel = NotificationChannel(
getString(R.string.notice_notification_channel_id),
getString(R.string.notice_notification_channel_name),
NotificationManager.IMPORTANCE_HIGH
)
noticeChannel.description = getString(R.string.notice_notification_channel_description)
noticeChannel.setSound(null, null)
// Register the channel with the system; you can't change the importance // Register the channel with the system; you can't change the importance
// or other notification behaviors after this // or other notification behaviors after this
val notificationManager = getSystemService(NotificationManager::class.java) val notificationManager = getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(channel) notificationManager.createNotificationChannel(emulationChannel)
notificationManager.createNotificationChannel(noticeChannel)
} }
override fun onCreate() { override fun onCreate() {
@ -42,8 +48,7 @@ class YuzuApplication : Application() {
GpuDriverHelper.initializeDriverParameters(applicationContext) GpuDriverHelper.initializeDriverParameters(applicationContext)
NativeLibrary.logDeviceInfo() NativeLibrary.logDeviceInfo()
// TODO(bunnei): Disable notifications until we support app suspension. createNotificationChannels();
//createNotificationChannel();
} }
companion object { companion object {

View file

@ -3,14 +3,21 @@
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments
import android.Manifest
import android.content.ActivityNotFoundException
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
import android.provider.DocumentsContract
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -19,6 +26,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
import org.yuzu.yuzu_emu.features.DocumentProvider
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.HomeSetting import org.yuzu.yuzu_emu.model.HomeSetting
@ -49,6 +57,11 @@ class HomeSettingsFragment : Fragment() {
R.string.settings_description, R.string.settings_description,
R.drawable.ic_settings R.drawable.ic_settings
) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }, ) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") },
HomeSetting(
R.string.open_user_folder,
R.string.open_user_folder_description,
R.drawable.ic_folder
) { openFileManager() },
HomeSetting( HomeSetting(
R.string.install_gpu_driver, R.string.install_gpu_driver,
R.string.install_gpu_driver_description, R.string.install_gpu_driver_description,
@ -84,6 +97,82 @@ class HomeSettingsFragment : Fragment() {
_binding = null _binding = null
} }
private fun openFileManager() {
// First, try to open the user data folder directly
try {
startActivity(getFileManagerIntentOnDocumentProvider(Intent.ACTION_VIEW))
return
} catch (_: ActivityNotFoundException) {}
try {
startActivity(getFileManagerIntentOnDocumentProvider("android.provider.action.BROWSE"))
return
} catch (_: ActivityNotFoundException) {}
// Just try to open the file manager, try the package name used on "normal" phones
try {
startActivity(getFileManagerIntent("com.google.android.documentsui"))
showNoLinkNotification()
return
} catch (_: ActivityNotFoundException) {}
try {
// Next, try the AOSP package name
startActivity(getFileManagerIntent("com.android.documentsui"))
showNoLinkNotification()
return
} catch (_: ActivityNotFoundException) {}
Toast.makeText(
requireContext(),
resources.getString(R.string.no_file_manager),
Toast.LENGTH_LONG
).show()
}
private fun getFileManagerIntent(packageName: String): Intent {
// Fragile, but some phones don't expose the system file manager in any better way
val intent = Intent(Intent.ACTION_MAIN)
intent.setClassName(packageName, "com.android.documentsui.files.FilesActivity")
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
return intent
}
private fun getFileManagerIntentOnDocumentProvider(action: String): Intent {
val authority = "${requireContext().packageName}.user"
val intent = Intent(action)
intent.addCategory(Intent.CATEGORY_DEFAULT)
intent.data = DocumentsContract.buildRootUri(authority, DocumentProvider.ROOT_ID)
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
return intent
}
private fun showNoLinkNotification() {
val builder = NotificationCompat.Builder(requireContext(), getString(R.string.notice_notification_channel_id))
.setSmallIcon(R.drawable.ic_stat_notification_logo)
.setContentTitle(getString(R.string.notification_no_directory_link))
.setContentText(getString(R.string.notification_no_directory_link_description))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
// TODO: Make the click action for this notification lead to a help article
with(NotificationManagerCompat.from(requireContext())) {
if (ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
Toast.makeText(
requireContext(),
resources.getString(R.string.notification_permission_not_granted),
Toast.LENGTH_LONG
).show()
return
}
notify(0, builder.build())
}
}
private fun driverInstaller() { private fun driverInstaller() {
// Get the driver name for the dialog message. // Get the driver name for the dialog message.
var driverName = GpuDriverHelper.customDriverName var driverName = GpuDriverHelper.customDriverName

View file

@ -4,11 +4,13 @@
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments
import android.content.Intent import android.content.Intent
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
@ -63,6 +65,10 @@ class SetupFragment : Fragment() {
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
pushNotificationPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS)
}
mainActivity = requireActivity() as MainActivity mainActivity = requireActivity() as MainActivity
homeViewModel.setNavigationVisibility(false) homeViewModel.setNavigationVisibility(false)
@ -219,6 +225,11 @@ class SetupFragment : Fragment() {
_binding = null _binding = null
} }
private val pushNotificationPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
// TODO: Show proper notification request reason and confirmation
}
private fun finishSetup() { private fun finishSetup() {
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
.putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false) .putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false)

View file

@ -29,10 +29,10 @@ class ForegroundService : Service() {
PendingIntent.FLAG_IMMUTABLE PendingIntent.FLAG_IMMUTABLE
) )
val builder = val builder =
NotificationCompat.Builder(this, getString(R.string.app_notification_channel_id)) NotificationCompat.Builder(this, getString(R.string.emulation_notification_channel_id))
.setSmallIcon(R.drawable.ic_stat_notification_logo) .setSmallIcon(R.drawable.ic_stat_notification_logo)
.setContentTitle(getString(R.string.app_name)) .setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.app_notification_running)) .setContentText(getString(R.string.emulation_notification_running))
.setPriority(NotificationCompat.PRIORITY_LOW) .setPriority(NotificationCompat.PRIORITY_LOW)
.setOngoing(true) .setOngoing(true)
.setVibrate(null) .setVibrate(null)

View file

@ -4,10 +4,14 @@
<!-- General application strings --> <!-- General application strings -->
<string name="app_name" translatable="false">yuzu</string> <string name="app_name" translatable="false">yuzu</string>
<string name="app_disclaimer">This software will run games for the Nintendo Switch game console. No game titles or keys are included.&lt;br /&gt;&lt;br /&gt;Before you begin, please locate your <![CDATA[<b> prod.keys </b>]]> file on your device storage.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href="https://yuzu-emu.org/help/quickstart">Learn more</a>]]></string> <string name="app_disclaimer">This software will run games for the Nintendo Switch game console. No game titles or keys are included.&lt;br /&gt;&lt;br /&gt;Before you begin, please locate your <![CDATA[<b> prod.keys </b>]]> file on your device storage.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href="https://yuzu-emu.org/help/quickstart">Learn more</a>]]></string>
<string name="app_notification_channel_name" translatable="false">yuzu</string> <string name="emulation_notification_channel_name">Emulation is Active</string>
<string name="app_notification_channel_id" translatable="false">yuzu</string> <string name="emulation_notification_channel_id" translatable="false">emulationIsActive</string>
<string name="app_notification_channel_description">yuzu Switch emulator notifications</string> <string name="emulation_notification_channel_description">Shows a persistent notification when emulation is running.</string>
<string name="app_notification_running">yuzu is running</string> <string name="emulation_notification_running">yuzu is running</string>
<string name="notice_notification_channel_name">Notices and errors</string>
<string name="notice_notification_channel_id" translatable="false">noticesAndErrors</string>
<string name="notice_notification_channel_description">Shows notifications when something goes wrong.</string>
<string name="notification_permission_not_granted">Notification permission not granted!</string>
<!-- Setup strings --> <!-- Setup strings -->
<string name="welcome">Welcome!</string> <string name="welcome">Welcome!</string>
@ -29,14 +33,14 @@
<!-- Home strings --> <!-- Home strings -->
<string name="home_games">Games</string> <string name="home_games">Games</string>
<string name="home_settings">Settings</string> <string name="home_settings">Settings</string>
<string name="select_games_folder">Select Games Folder</string> <string name="select_games_folder">Select games folder</string>
<string name="select_games_folder_description">Allows yuzu to populate the games list</string> <string name="select_games_folder_description">Allows yuzu to populate the games list</string>
<string name="add_games_warning">Skip selecting games folder?</string> <string name="add_games_warning">Skip selecting games folder?</string>
<string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string> <string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string>
<string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string> <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
<string name="home_search_games">Search Games</string> <string name="home_search_games">Search Games</string>
<string name="games_dir_selected">Games directory selected</string> <string name="games_dir_selected">Games directory selected</string>
<string name="install_prod_keys">Install Prod.keys</string> <string name="install_prod_keys">Install prod.keys</string>
<string name="install_prod_keys_description">Required to decrypt retail games</string> <string name="install_prod_keys_description">Required to decrypt retail games</string>
<string name="install_prod_keys_warning">Skip adding keys?</string> <string name="install_prod_keys_warning">Skip adding keys?</string>
<string name="install_prod_keys_warning_description">Valid keys are required to emulate retail games. Only homebrew apps will function if you continue.</string> <string name="install_prod_keys_warning_description">Valid keys are required to emulate retail games. Only homebrew apps will function if you continue.</string>
@ -44,16 +48,21 @@
<string name="warning_help">Help</string> <string name="warning_help">Help</string>
<string name="warning_skip">Skip</string> <string name="warning_skip">Skip</string>
<string name="warning_cancel">Cancel</string> <string name="warning_cancel">Cancel</string>
<string name="install_amiibo_keys">Install Amiibo Keys</string> <string name="install_amiibo_keys">Install Amiibo keys</string>
<string name="install_amiibo_keys_description">Required to use Amiibo in game</string> <string name="install_amiibo_keys_description">Required to use Amiibo in game</string>
<string name="invalid_keys_file">Invalid keys file selected</string> <string name="invalid_keys_file">Invalid keys file selected</string>
<string name="install_keys_success">Keys successfully installed</string> <string name="install_keys_success">Keys successfully installed</string>
<string name="install_keys_failure">Keys file (prod.keys) is invalid</string> <string name="install_keys_failure">Keys file (prod.keys) is invalid</string>
<string name="install_amiibo_keys_failure">Keys file (key_retail.bin) is invalid</string> <string name="install_amiibo_keys_failure">Keys file (key_retail.bin) is invalid</string>
<string name="install_gpu_driver">Install GPU Driver</string> <string name="install_gpu_driver">Install GPU driver</string>
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string> <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
<string name="advanced_settings">Advanced Settings</string> <string name="advanced_settings">Advanced settings</string>
<string name="settings_description">Configure emulator settings</string> <string name="settings_description">Configure emulator settings</string>
<string name="open_user_folder">Open yuzu folder</string>
<string name="open_user_folder_description">Manage yuzu\'s internal files</string>
<string name="no_file_manager">No file manager found</string>
<string name="notification_no_directory_link">Couldn\'t open yuzu directory</string>
<string name="notification_no_directory_link_description">Please locate the user folder with the file manager\'s side panel manually.</string>
<!-- General settings strings --> <!-- General settings strings -->
<string name="frame_limit_enable">Enable limit speed</string> <string name="frame_limit_enable">Enable limit speed</string>