summaryrefslogtreecommitdiff
path: root/src/android
diff options
context:
space:
mode:
Diffstat (limited to 'src/android')
-rw-r--r--src/android/app/build.gradle.kts2
-rw-r--r--src/android/app/src/main/AndroidManifest.xml2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt13
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt17
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt35
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt48
-rw-r--r--src/android/app/src/main/jni/config.cpp5
-rw-r--r--src/android/app/src/main/jni/native.cpp46
8 files changed, 111 insertions, 57 deletions
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index a8db70511..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")
}
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index 6184f3eb6..933244140 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"
@@ -55,7 +56,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
android:theme="@style/Theme.Yuzu.Main"
android:launchMode="singleTop"
- android:screenOrientation="userLandscape"
android:supportsPictureInPicture="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
android:exported="true">
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
index a5af5a7ae..e6fffc832 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
@@ -11,7 +11,6 @@ import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
-import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
@@ -246,17 +245,5 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
settings.putExtra(ARG_GAME_ID, gameId)
context.startActivity(settings)
}
-
- fun launch(
- context: Context,
- launcher: ActivityResultLauncher<Intent>,
- menuTag: String?,
- gameId: String?
- ) {
- val settings = Intent(context, SettingsActivity::class.java)
- settings.putExtra(ARG_MENU_TAG, menuTag)
- settings.putExtra(ARG_GAME_ID, gameId)
- launcher.launch(settings)
- }
}
}
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 25b9d4018..09e93a017 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
@@ -7,7 +7,6 @@ import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
-import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.ActivityInfo
import android.content.res.Configuration
@@ -19,8 +18,6 @@ import android.util.Rational
import android.view.*
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
-import androidx.activity.result.ActivityResultLauncher
-import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.Insets
@@ -66,8 +63,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private var isInFoldableLayout = false
- private lateinit var onReturnFromSettings: ActivityResultLauncher<Intent>
-
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is EmulationActivity) {
@@ -81,11 +76,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
.collect { updateFoldableLayout(context, it) }
}
}
-
- onReturnFromSettings = context.activityResultRegistry.register(
- "SettingsResult",
- ActivityResultContracts.StartActivityForResult()
- ) { updateScreenLayout() }
} else {
throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
}
@@ -149,12 +139,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
R.id.menu_settings -> {
- SettingsActivity.launch(
- requireContext(),
- onReturnFromSettings,
- SettingsFile.FILE_NAME_CONFIG,
- ""
- )
+ SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "")
true
}
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/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt
index 685ccaa76..2f0868c63 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt
@@ -7,7 +7,6 @@ import android.content.Context
import android.util.AttributeSet
import android.util.Rational
import android.view.SurfaceView
-import kotlin.math.roundToInt
class FixedRatioSurfaceView @JvmOverloads constructor(
context: Context,
@@ -22,27 +21,44 @@ class FixedRatioSurfaceView @JvmOverloads constructor(
*/
fun setAspectRatio(ratio: Rational?) {
aspectRatio = ratio?.toFloat() ?: 0f
+ requestLayout()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
- val width = MeasureSpec.getSize(widthMeasureSpec)
- val height = MeasureSpec.getSize(heightMeasureSpec)
+ val displayWidth: Float = MeasureSpec.getSize(widthMeasureSpec).toFloat()
+ val displayHeight: Float = MeasureSpec.getSize(heightMeasureSpec).toFloat()
if (aspectRatio != 0f) {
- val newWidth: Int
- val newHeight: Int
- if (height * aspectRatio < width) {
- newWidth = (height * aspectRatio).roundToInt()
- newHeight = height
+ val displayAspect = displayWidth / displayHeight
+ if (displayAspect < aspectRatio) {
+ // Max out width
+ val halfHeight = displayHeight / 2
+ val surfaceHeight = displayWidth / aspectRatio
+ val newTop: Float = halfHeight - (surfaceHeight / 2)
+ val newBottom: Float = halfHeight + (surfaceHeight / 2)
+ super.onMeasure(
+ widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(
+ newBottom.toInt() - newTop.toInt(),
+ MeasureSpec.EXACTLY
+ )
+ )
+ return
} else {
- newWidth = width
- newHeight = (width / aspectRatio).roundToInt()
+ // Max out height
+ val halfWidth = displayWidth / 2
+ val surfaceWidth = displayHeight * aspectRatio
+ val newLeft: Float = halfWidth - (surfaceWidth / 2)
+ val newRight: Float = halfWidth + (surfaceWidth / 2)
+ super.onMeasure(
+ MeasureSpec.makeMeasureSpec(
+ newRight.toInt() - newLeft.toInt(),
+ MeasureSpec.EXACTLY
+ ),
+ heightMeasureSpec
+ )
+ return
}
- val left = (width - newWidth) / 2
- val top = (height - newHeight) / 2
- setLeftTopRightBottom(left, top, left + newWidth, top + newHeight)
- } else {
- setLeftTopRightBottom(0, 0, width, height)
}
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp
index 5e1f10f99..9de9bd93e 100644
--- a/src/android/app/src/main/jni/config.cpp
+++ b/src/android/app/src/main/jni/config.cpp
@@ -11,6 +11,7 @@
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/hle/service/acc/profile_manager.h"
#include "input_common/main.h"
#include "jni/config.h"
@@ -144,7 +145,9 @@ void Config::ReadValues() {
Service::Account::MAX_USERS - 1);
// Disable docked mode by default on Android
- Settings::values.use_docked_mode = config->GetBoolean("System", "use_docked_mode", false);
+ Settings::values.use_docked_mode.SetValue(config->GetBoolean("System", "use_docked_mode", false)
+ ? Settings::ConsoleMode::Docked
+ : Settings::ConsoleMode::Handheld);
const auto rng_seed_enabled = config->GetBoolean("System", "rng_seed_enabled", false);
if (rng_seed_enabled) {
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index c23b2f19e..7e17833a0 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>();
@@ -377,7 +420,7 @@ public:
return false;
}
- return !Settings::values.use_docked_mode.GetValue();
+ return !Settings::IsDockedMode();
}
void SetDeviceType([[maybe_unused]] int index, int type) {
@@ -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;