mirror of
				https://git.suyu.dev/suyu/suyu.git
				synced 2025-10-31 14:56:40 +08:00 
			
		
		
		
	android: Add user directory shortcut
This commit is contained in:
		
							parent
							
								
									265b9139e0
								
							
						
					
					
						commit
						912bf6a0c6
					
				| @ -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" | ||||||
|  | |||||||
| @ -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 { | ||||||
|  | |||||||
| @ -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 | ||||||
|  | |||||||
| @ -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) | ||||||
|  | |||||||
| @ -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) | ||||||
|  | |||||||
| @ -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.<br /><br />Before you begin, please locate your <![CDATA[<b> prod.keys </b>]]> file on your device storage.<br /><br /><![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.<br /><br />Before you begin, please locate your <![CDATA[<b> prod.keys </b>]]> file on your device storage.<br /><br /><![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> | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user