summaryrefslogtreecommitdiff
path: root/src/android/app
diff options
context:
space:
mode:
Diffstat (limited to 'src/android/app')
-rw-r--r--src/android/app/build.gradle.kts7
-rw-r--r--src/android/app/src/main/AndroidManifest.xml1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt24
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt12
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt19
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt35
-rw-r--r--src/android/app/src/main/jni/native.cpp44
-rw-r--r--src/android/app/src/main/res/layout/card_home_option.xml17
11 files changed, 155 insertions, 15 deletions
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index 9a47e2bd8..fe79a701c 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -95,6 +95,7 @@ android {
// builds a release build that doesn't need signing
// Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
register("relWithDebInfo") {
+ isDefault = true
resValue("string", "app_name_suffixed", "yuzu Debug Release")
signingConfig = signingConfigs.getByName("debug")
isMinifyEnabled = true
@@ -122,6 +123,7 @@ android {
flavorDimensions.add("version")
productFlavors {
create("mainline") {
+ isDefault = true
dimension = "version"
buildConfigField("Boolean", "PREMIUM", "false")
}
@@ -160,6 +162,11 @@ android {
}
}
+tasks.create<Delete>("ktlintReset") {
+ delete(File(buildDir.path + File.separator + "intermediates/ktLint"))
+}
+
+tasks.getByPath("loadKtlintReporters").dependsOn("ktlintReset")
tasks.getByPath("preBuild").dependsOn("ktlintCheck")
ktlint {
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index 6184f3eb6..36e2dac98 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -25,6 +25,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
android:hasFragileUserData="false"
android:supportsRtl="true"
android:isGame="true"
+ android:appCategory="game"
android:localeConfig="@xml/locales_config"
android:banner="@drawable/tv_banner"
android:extractNativeLibs="true"
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
index aadc445f9..9f859b442 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
@@ -3,19 +3,25 @@
package org.yuzu.yuzu_emu.adapters
+import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
+import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.HomeSetting
-class HomeSettingAdapter(private val activity: AppCompatActivity, var options: List<HomeSetting>) :
+class HomeSettingAdapter(
+ private val activity: AppCompatActivity,
+ private val viewLifecycle: LifecycleOwner,
+ var options: List<HomeSetting>
+) :
RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(),
View.OnClickListener {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
@@ -79,6 +85,22 @@ class HomeSettingAdapter(private val activity: AppCompatActivity, var options: L
binding.optionDescription.alpha = 0.5f
binding.optionIcon.alpha = 0.5f
}
+
+ option.details.observe(viewLifecycle) { updateOptionDetails(it) }
+ binding.optionDetail.postDelayed(
+ {
+ binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE
+ binding.optionDetail.isSelected = true
+ },
+ 3000
+ )
+ }
+
+ private fun updateOptionDetails(detailString: String) {
+ if (detailString.isNotEmpty()) {
+ binding.optionDetail.text = detailString
+ binding.optionDetail.visibility = View.VISIBLE
+ }
}
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index 0e7c1ba88..25b9d4018 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -297,11 +297,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
emulationActivity?.let {
it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) {
Settings.LayoutOption_MobileLandscape ->
- ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
+ ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
Settings.LayoutOption_MobilePortrait ->
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
- else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
+ else -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
}
}
}
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 c001af892..d5e793491 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
@@ -129,7 +129,11 @@ class HomeSettingsFragment : Fragment() {
mainActivity.getGamesDirectory.launch(
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
)
- }
+ },
+ { true },
+ 0,
+ 0,
+ homeViewModel.gamesDir
)
)
add(
@@ -201,7 +205,11 @@ class HomeSettingsFragment : Fragment() {
binding.homeSettingsList.apply {
layoutManager = LinearLayoutManager(requireContext())
- adapter = HomeSettingAdapter(requireActivity() as AppCompatActivity, optionsList)
+ adapter = HomeSettingAdapter(
+ requireActivity() as AppCompatActivity,
+ viewLifecycleOwner,
+ optionsList
+ )
}
setInsets()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
index 522d07c37..498c222fa 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
@@ -3,6 +3,9 @@
package org.yuzu.yuzu_emu.model
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+
data class HomeSetting(
val titleId: Int,
val descriptionId: Int,
@@ -10,5 +13,6 @@ data class HomeSetting(
val onClick: () -> Unit,
val isEnabled: () -> Boolean = { true },
val disabledTitleId: Int = 0,
- val disabledMessageId: Int = 0
+ val disabledMessageId: Int = 0,
+ val details: LiveData<String> = MutableLiveData("")
)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
index e13d84c9c..a48ef7a88 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
@@ -3,9 +3,15 @@
package org.yuzu.yuzu_emu.model
+import android.net.Uri
+import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.preference.PreferenceManager
+import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.utils.GameHelper
class HomeViewModel : ViewModel() {
private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>()
@@ -17,6 +23,14 @@ class HomeViewModel : ViewModel() {
private val _shouldPageForward = MutableLiveData(false)
val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward
+ private val _gamesDir = MutableLiveData(
+ Uri.parse(
+ PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
+ .getString(GameHelper.KEY_GAME_PATH, "")
+ ).path ?: ""
+ )
+ val gamesDir: LiveData<String> get() = _gamesDir
+
var navigatedToSetup = false
init {
@@ -40,4 +54,9 @@ class HomeViewModel : ViewModel() {
fun setShouldPageForward(pageForward: Boolean) {
_shouldPageForward.value = pageForward
}
+
+ fun setGamesDir(activity: FragmentActivity, dir: String) {
+ ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
+ _gamesDir.value = dir
+ }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index f77d06262..aaf3a0ec1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -290,6 +290,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
).show()
gamesViewModel.reloadGames(true)
+ homeViewModel.setGamesDir(this, result.path!!)
}
val getProdKey =
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
index f8e7eeca7..f71d0a098 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
@@ -11,6 +11,7 @@ import kotlinx.serialization.json.Json
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.Game
+import org.yuzu.yuzu_emu.model.MinimalDocumentFile
object GameHelper {
const val KEY_GAME_PATH = "game_path"
@@ -29,15 +30,7 @@ object GameHelper {
// Ensure keys are loaded so that ROM metadata can be decrypted.
NativeLibrary.reloadKeys()
- val children = FileUtil.listFiles(context, gamesUri)
- for (file in children) {
- if (!file.isDirectory) {
- // Check that the file has an extension we care about before trying to read out of it.
- if (Game.extensions.contains(FileUtil.getExtension(file.uri))) {
- games.add(getGame(file.uri))
- }
- }
- }
+ addGamesRecursive(games, FileUtil.listFiles(context, gamesUri), 3)
// Cache list of games found on disk
val serializedGames = mutableSetOf<String>()
@@ -52,6 +45,30 @@ object GameHelper {
return games.toList()
}
+ private fun addGamesRecursive(
+ games: MutableList<Game>,
+ files: Array<MinimalDocumentFile>,
+ depth: Int
+ ) {
+ if (depth <= 0) {
+ return
+ }
+
+ files.forEach {
+ if (it.isDirectory) {
+ addGamesRecursive(
+ games,
+ FileUtil.listFiles(YuzuApplication.appContext, it.uri),
+ depth - 1
+ )
+ } else {
+ if (Game.extensions.contains(FileUtil.getExtension(it.uri))) {
+ games.add(getGame(it.uri))
+ }
+ }
+ }
+ }
+
private fun getGame(uri: Uri): Game {
val filePath = uri.toString()
var name = NativeLibrary.getTitle(filePath)
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index c23b2f19e..8b99d1d6e 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -30,6 +30,7 @@
#include "core/cpu_manager.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h"
+#include "core/file_sys/content_archive.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/submission_package.h"
#include "core/file_sys/vfs.h"
@@ -224,6 +225,42 @@ public:
m_system.Renderer().NotifySurfaceChanged();
}
+ void ConfigureFilesystemProvider(const std::string& filepath) {
+ const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read);
+ if (!file) {
+ return;
+ }
+
+ auto loader = Loader::GetLoader(m_system, file);
+ if (!loader) {
+ return;
+ }
+
+ const auto file_type = loader->GetFileType();
+ if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
+ return;
+ }
+
+ u64 program_id = 0;
+ const auto res2 = loader->ReadProgramId(program_id);
+ if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
+ m_manual_provider->AddEntry(FileSys::TitleType::Application,
+ FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
+ program_id, file);
+ } else if (res2 == Loader::ResultStatus::Success &&
+ (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
+ const auto nsp = file_type == Loader::FileType::NSP
+ ? std::make_shared<FileSys::NSP>(file)
+ : FileSys::XCI{file}.GetSecurePartitionNSP();
+ for (const auto& title : nsp->GetNCAs()) {
+ for (const auto& entry : title.second) {
+ m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
+ entry.second->GetBaseFile());
+ }
+ }
+ }
+ }
+
Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
std::scoped_lock lock(m_mutex);
@@ -254,8 +291,14 @@ public:
std::move(android_keyboard), // Software Keyboard
nullptr, // Web Browser
});
+
+ // Initialize filesystem.
+ m_manual_provider = std::make_unique<FileSys::ManualContentProvider>();
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
+ m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
+ m_manual_provider.get());
m_system.GetFileSystemController().CreateFactories(*m_vfs);
+ ConfigureFilesystemProvider(filepath);
// Initialize account manager
m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
@@ -489,6 +532,7 @@ private:
bool m_is_paused{};
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
+ std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
// GPU driver parameters
std::shared_ptr<Common::DynamicLibrary> m_vulkan_library;
diff --git a/src/android/app/src/main/res/layout/card_home_option.xml b/src/android/app/src/main/res/layout/card_home_option.xml
index dc289db17..f9f1d89fb 100644
--- a/src/android/app/src/main/res/layout/card_home_option.xml
+++ b/src/android/app/src/main/res/layout/card_home_option.xml
@@ -53,6 +53,23 @@
android:layout_marginTop="5dp"
tools:text="@string/install_prod_keys_description" />
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.LabelMedium"
+ android:id="@+id/option_detail"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAlignment="viewStart"
+ android:textSize="14sp"
+ android:textStyle="bold"
+ android:singleLine="true"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:ellipsize="none"
+ android:requiresFadingEdge="horizontal"
+ android:layout_marginTop="5dp"
+ android:visibility="gone"
+ tools:visibility="visible"
+ tools:text="/tree/primary:Games" />
+
</LinearLayout>
</LinearLayout>