summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/android/app/src/main/AndroidManifest.xml1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt33
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt89
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt4
-rw-r--r--src/android/app/src/main/res/values/strings.xml27
6 files changed, 140 insertions, 25 deletions
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index 4791e3417..ae473ae3a 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -15,6 +15,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.NFC" />
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:name="org.yuzu.yuzu_emu.YuzuApplication"
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
index f987c6b7b..5848c87c1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
@@ -15,23 +15,29 @@ import java.io.File
fun Context.getPublicFilesDir() : File = getExternalFilesDir(null) ?: filesDir
class YuzuApplication : Application() {
- private fun createNotificationChannel() {
- // Create the NotificationChannel, but only on API 26+ because
- // the NotificationChannel class is new and not in the support library
- val name: CharSequence = getString(R.string.app_notification_channel_name)
- val description = getString(R.string.app_notification_channel_description)
- val channel = NotificationChannel(
- getString(R.string.app_notification_channel_id),
- name,
+ private fun createNotificationChannels() {
+ val emulationChannel = NotificationChannel(
+ getString(R.string.emulation_notification_channel_id),
+ getString(R.string.emulation_notification_channel_name),
NotificationManager.IMPORTANCE_LOW
)
- channel.description = description
- channel.setSound(null, null)
- channel.vibrationPattern = null
+ emulationChannel.description = getString(R.string.emulation_notification_channel_description)
+ emulationChannel.setSound(null, 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
// or other notification behaviors after this
val notificationManager = getSystemService(NotificationManager::class.java)
- notificationManager.createNotificationChannel(channel)
+ notificationManager.createNotificationChannel(emulationChannel)
+ notificationManager.createNotificationChannel(noticeChannel)
}
override fun onCreate() {
@@ -42,8 +48,7 @@ class YuzuApplication : Application() {
GpuDriverHelper.initializeDriverParameters(applicationContext)
NativeLibrary.logDeviceInfo()
- // TODO(bunnei): Disable notifications until we support app suspension.
- //createNotificationChannel();
+ createNotificationChannels();
}
companion object {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index 23d2da4d0..0e7c181ea 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -3,14 +3,21 @@
package org.yuzu.yuzu_emu.fragments
+import android.Manifest
+import android.content.ActivityNotFoundException
import android.content.DialogInterface
import android.content.Intent
+import android.content.pm.PackageManager
import android.os.Bundle
+import android.provider.DocumentsContract
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
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.WindowInsetsCompat
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.adapters.HomeSettingAdapter
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.utils.SettingsFile
import org.yuzu.yuzu_emu.model.HomeSetting
@@ -50,6 +58,11 @@ class HomeSettingsFragment : Fragment() {
R.drawable.ic_settings
) { 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(
R.string.install_gpu_driver,
R.string.install_gpu_driver_description,
R.drawable.ic_input
@@ -84,6 +97,82 @@ class HomeSettingsFragment : Fragment() {
_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() {
// Get the driver name for the dialog message.
var driverName = GpuDriverHelper.customDriverName
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
index 2af78b02c..3d2f8719c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
@@ -4,11 +4,13 @@
package org.yuzu.yuzu_emu.fragments
import android.content.Intent
+import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.OnBackPressedCallback
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
@@ -63,6 +65,10 @@ class SetupFragment : Fragment() {
}
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
homeViewModel.setNavigationVisibility(false)
@@ -219,6 +225,11 @@ class SetupFragment : Fragment() {
_binding = null
}
+ private val pushNotificationPermissionLauncher =
+ registerForActivityResult(ActivityResultContracts.RequestPermission()) {
+ // TODO: Show proper notification request reason and confirmation
+ }
+
private fun finishSetup() {
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
.putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt
index 557fe0738..7e33ff044 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt
@@ -29,10 +29,10 @@ class ForegroundService : Service() {
PendingIntent.FLAG_IMMUTABLE
)
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)
.setContentTitle(getString(R.string.app_name))
- .setContentText(getString(R.string.app_notification_running))
+ .setContentText(getString(R.string.emulation_notification_running))
.setPriority(NotificationCompat.PRIORITY_LOW)
.setOngoing(true)
.setVibrate(null)
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 8220cd412..58ae37790 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -4,10 +4,14 @@
<!-- General application strings -->
<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_notification_channel_name" translatable="false">yuzu</string>
- <string name="app_notification_channel_id" translatable="false">yuzu</string>
- <string name="app_notification_channel_description">yuzu Switch emulator notifications</string>
- <string name="app_notification_running">yuzu is running</string>
+ <string name="emulation_notification_channel_name">Emulation is Active</string>
+ <string name="emulation_notification_channel_id" translatable="false">emulationIsActive</string>
+ <string name="emulation_notification_channel_description">Shows a persistent notification when emulation 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 -->
<string name="welcome">Welcome!</string>
@@ -29,14 +33,14 @@
<!-- Home strings -->
<string name="home_games">Games</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="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_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
<string name="home_search_games">Search Games</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_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>
@@ -44,16 +48,21 @@
<string name="warning_help">Help</string>
<string name="warning_skip">Skip</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="invalid_keys_file">Invalid keys file selected</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_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="advanced_settings">Advanced Settings</string>
+ <string name="advanced_settings">Advanced 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 -->
<string name="frame_limit_enable">Enable limit speed</string>