diff options
Diffstat (limited to 'src/android')
12 files changed, 177 insertions, 76 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index 0fb35bf98..55abba093 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -303,6 +303,11 @@ object NativeLibrary { */ external fun getCpuBackend(): String + /** + * Returns the current GPU Driver. + */ + external fun getGpuDriver(): String + external fun applySettings() external fun logSettings() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt index f006f9e3d..0ab1b46c3 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt @@ -14,15 +14,20 @@ import androidx.recyclerview.widget.RecyclerView * Generic adapter that implements an [AsyncDifferConfig] and covers some of the basic boilerplate * code used in every [RecyclerView]. * Type assigned to [Model] must inherit from [Object] in order to be compared properly. + * @param exact Decides whether each item will be compared by reference or by their contents */ -abstract class AbstractDiffAdapter<Model : Any, Holder : AbstractViewHolder<Model>> : - ListAdapter<Model, Holder>(AsyncDifferConfig.Builder(DiffCallback<Model>()).build()) { +abstract class AbstractDiffAdapter<Model : Any, Holder : AbstractViewHolder<Model>>( + exact: Boolean = true +) : ListAdapter<Model, Holder>(AsyncDifferConfig.Builder(DiffCallback<Model>(exact)).build()) { override fun onBindViewHolder(holder: Holder, position: Int) = holder.bind(currentList[position]) - private class DiffCallback<Model> : DiffUtil.ItemCallback<Model>() { + private class DiffCallback<Model>(val exact: Boolean) : DiffUtil.ItemCallback<Model>() { override fun areItemsTheSame(oldItem: Model & Any, newItem: Model & Any): Boolean { - return oldItem === newItem + if (exact) { + return oldItem === newItem + } + return oldItem == newItem } @SuppressLint("DiffUtilEquals") diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index e26c2e0ab..85c8249e6 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt @@ -3,9 +3,6 @@ package org.yuzu.yuzu_emu.adapters -import android.content.Intent -import android.graphics.Bitmap -import android.graphics.drawable.LayerDrawable import android.net.Uri import android.text.TextUtils import android.view.LayoutInflater @@ -15,10 +12,6 @@ import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat -import androidx.core.content.res.ResourcesCompat -import androidx.core.graphics.drawable.IconCompat -import androidx.core.graphics.drawable.toBitmap -import androidx.core.graphics.drawable.toDrawable import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope @@ -30,7 +23,6 @@ import kotlinx.coroutines.withContext import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication -import org.yuzu.yuzu_emu.activities.EmulationActivity import org.yuzu.yuzu_emu.databinding.CardGameBinding import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.GamesViewModel @@ -38,7 +30,7 @@ import org.yuzu.yuzu_emu.utils.GameIconUtils import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder class GameAdapter(private val activity: AppCompatActivity) : - AbstractDiffAdapter<Game, GameAdapter.GameViewHolder>() { + AbstractDiffAdapter<Game, GameAdapter.GameViewHolder>(exact = false) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder { CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false) .also { return GameViewHolder(it) } @@ -89,36 +81,13 @@ class GameAdapter(private val activity: AppCompatActivity) : ) .apply() - val openIntent = - Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply { - action = Intent.ACTION_VIEW - data = Uri.parse(game.path) - } - activity.lifecycleScope.launch { withContext(Dispatchers.IO) { - val layerDrawable = ResourcesCompat.getDrawable( - YuzuApplication.appContext.resources, - R.drawable.shortcut, - null - ) as LayerDrawable - layerDrawable.setDrawableByLayerId( - R.id.shortcut_foreground, - GameIconUtils.getGameIcon(activity, game) - .toDrawable(YuzuApplication.appContext.resources) - ) - val inset = YuzuApplication.appContext.resources - .getDimensionPixelSize(R.dimen.icon_inset) - layerDrawable.setLayerInset(1, inset, inset, inset, inset) val shortcut = ShortcutInfoCompat.Builder(YuzuApplication.appContext, game.path) .setShortLabel(game.title) - .setIcon( - IconCompat.createWithAdaptiveBitmap( - layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888) - ) - ) - .setIntent(openIntent) + .setIcon(GameIconUtils.getShortcutIcon(activity, game)) + .setIntent(game.launchIntent) .build() ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut) } 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 2a97ae14d..d17e087fe 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 @@ -38,7 +38,6 @@ import androidx.window.layout.WindowLayoutInfo import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.slider.Slider import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import org.yuzu.yuzu_emu.HomeNavigationDirections @@ -141,7 +140,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { // So this fragment doesn't restart on configuration changes; i.e. rotation. retainInstance = true - emulationState = EmulationState(game.path) + emulationState = EmulationState(game.path) { + return@EmulationState driverViewModel.isInteractionAllowed.value + } } /** @@ -371,6 +372,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } launch { + repeatOnLifecycle(Lifecycle.State.RESUMED) { + driverViewModel.isInteractionAllowed.collect { + if (it) { + startEmulation() + } + } + } + } + launch { repeatOnLifecycle(Lifecycle.State.CREATED) { emulationViewModel.emulationStarted.collectLatest { if (it) { @@ -398,19 +408,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } } - launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - driverViewModel.isInteractionAllowed.collect { - if (it) { - onEmulationStart() - } - } - } - } } } - private fun onEmulationStart() { + private fun startEmulation() { if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) { if (!DirectoryInitialization.areDirectoriesReady) { DirectoryInitialization.start() @@ -485,12 +486,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { val FRAMETIME = 2 val SPEED = 3 perfStatsUpdater = { - if (emulationViewModel.emulationStarted.value) { + if (emulationViewModel.emulationStarted.value && + !emulationViewModel.isEmulationStopping.value + ) { val perfStats = NativeLibrary.getPerfStats() val cpuBackend = NativeLibrary.getCpuBackend() + val gpuDriver = NativeLibrary.getGpuDriver() if (_binding != null) { binding.showFpsText.text = - String.format("FPS: %.1f\n%s", perfStats[FPS], cpuBackend) + String.format("FPS: %.1f\n%s/%s", perfStats[FPS], cpuBackend, gpuDriver) } perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800) } @@ -807,7 +811,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } - private class EmulationState(private val gamePath: String) { + private class EmulationState( + private val gamePath: String, + private val emulationCanStart: () -> Boolean + ) { private var state: State private var surface: Surface? = null @@ -901,6 +908,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { State.PAUSED -> Log.warning( "[EmulationFragment] Surface cleared while emulation paused." ) + else -> Log.warning( "[EmulationFragment] Surface cleared while emulation stopped." ) @@ -910,6 +918,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { private fun runWithValidSurface() { NativeLibrary.surfaceChanged(surface) + if (!emulationCanStart.invoke()) { + return + } + when (state) { State.STOPPED -> { val emulationThread = Thread({ diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt index 83a845434..582df0133 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt @@ -4,6 +4,8 @@ package org.yuzu.yuzu_emu.fragments import android.annotation.SuppressLint +import android.content.pm.ShortcutInfo +import android.content.pm.ShortcutManager import android.os.Bundle import android.text.TextUtils import android.view.LayoutInflater @@ -84,6 +86,24 @@ class GamePropertiesFragment : Fragment() { view.findNavController().popBackStack() } + val shortcutManager = requireActivity().getSystemService(ShortcutManager::class.java) + binding.buttonShortcut.isEnabled = shortcutManager.isRequestPinShortcutSupported + binding.buttonShortcut.setOnClickListener { + viewLifecycleOwner.lifecycleScope.launch { + withContext(Dispatchers.IO) { + val shortcut = ShortcutInfo.Builder(requireContext(), args.game.title) + .setShortLabel(args.game.title) + .setIcon( + GameIconUtils.getShortcutIcon(requireActivity(), args.game) + .toIcon(requireContext()) + ) + .setIntent(args.game.launchIntent) + .build() + shortcutManager.requestPinShortcut(shortcut, null) + } + } + } + GameIconUtils.loadGameIcon(args.game, binding.imageGameScreen) binding.title.text = args.game.title binding.title.postDelayed( diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt index 15ae3a42b..5ed754c96 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt @@ -144,6 +144,7 @@ class DriverViewModel : ViewModel() { val selectedDriverFile = File(StringSetting.DRIVER_PATH.getString()) val selectedDriverMetadata = GpuDriverHelper.customDriverSettingData if (GpuDriverHelper.installedCustomDriverData == selectedDriverMetadata) { + setDriverReady() return } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt index f1ea1e20f..6859b7780 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt @@ -3,6 +3,7 @@ package org.yuzu.yuzu_emu.model +import android.content.Intent import android.net.Uri import android.os.Parcelable import java.util.HashSet @@ -11,6 +12,7 @@ import kotlinx.serialization.Serializable import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication +import org.yuzu.yuzu_emu.activities.EmulationActivity import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.FileUtil import java.time.LocalDateTime @@ -61,12 +63,26 @@ class Game( val addonDir: String get() = DirectoryInitialization.userDirectory + "/load/" + programIdHex + "/" - override fun equals(other: Any?): Boolean { - if (other !is Game) { - return false + val launchIntent: Intent + get() = Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply { + action = Intent.ACTION_VIEW + data = Uri.parse(path) } - return hashCode() == other.hashCode() + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Game + + if (title != other.title) return false + if (path != other.path) return false + if (programId != other.programId) return false + if (developer != other.developer) return false + if (version != other.version) return false + if (isHomebrew != other.isHomebrew) return false + + return true } override fun hashCode(): Int { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt index 2e9b0beb8..d05020560 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt @@ -5,7 +5,10 @@ package org.yuzu.yuzu_emu.utils import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.graphics.drawable.LayerDrawable import android.widget.ImageView +import androidx.core.content.res.ResourcesCompat +import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toDrawable import androidx.lifecycle.LifecycleOwner @@ -85,4 +88,22 @@ object GameIconUtils { return imageLoader.execute(request) .drawable!!.toBitmap(config = Bitmap.Config.ARGB_8888) } + + suspend fun getShortcutIcon(lifecycleOwner: LifecycleOwner, game: Game): IconCompat { + val layerDrawable = ResourcesCompat.getDrawable( + YuzuApplication.appContext.resources, + R.drawable.shortcut, + null + ) as LayerDrawable + layerDrawable.setDrawableByLayerId( + R.id.shortcut_foreground, + getGameIcon(lifecycleOwner, game).toDrawable(YuzuApplication.appContext.resources) + ) + val inset = YuzuApplication.appContext.resources + .getDimensionPixelSize(R.dimen.icon_inset) + layerDrawable.setLayerInset(1, inset, inset, inset, inset) + return IconCompat.createWithAdaptiveBitmap( + layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888) + ) + } } diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index e51453eca..247f2c2b3 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -247,6 +247,7 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string m_system.GetCpuManager().OnGpuReady(); m_system.RegisterExitCallback([&] { HaltEmulation(); }); + OnEmulationStarted(); return Core::SystemResultStatus::Success; } @@ -674,6 +675,11 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuBackend(JNIEnv* env, jclass return ToJString(env, "JIT"); } +jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGpuDriver(JNIEnv* env, jobject jobj) { + return ToJString(env, + EmulationSession::GetInstance().System().GPU().Renderer().GetDeviceVendor()); +} + void Java_org_yuzu_yuzu_1emu_NativeLibrary_applySettings(JNIEnv* env, jobject jobj) { EmulationSession::GetInstance().System().ApplySettings(); } diff --git a/src/android/app/src/main/res/drawable/ic_shortcut.xml b/src/android/app/src/main/res/drawable/ic_shortcut.xml new file mode 100644 index 000000000..06e1983b2 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_shortcut.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960"> + <path + android:fillColor="?attr/colorControlNormal" + android:pathData="M280,920q-33,0 -56.5,-23.5T200,840v-720q0,-33 23.5,-56.5T280,40h400q33,0 56.5,23.5T760,120v160h-80v-40L280,240v480h400v-40h80v160q0,33 -23.5,56.5T680,920L280,920ZM686,520L480,520v120h-80v-120q0,-33 23.5,-56.5T480,440h206l-62,-64 56,-56 160,160 -160,160 -56,-56 62,-64Z" /> +</vector> diff --git a/src/android/app/src/main/res/layout-w600dp/fragment_game_properties.xml b/src/android/app/src/main/res/layout-w600dp/fragment_game_properties.xml index 0b9633855..551f255c0 100644 --- a/src/android/app/src/main/res/layout-w600dp/fragment_game_properties.xml +++ b/src/android/app/src/main/res/layout-w600dp/fragment_game_properties.xml @@ -43,16 +43,35 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> - <Button - android:id="@+id/button_back" - style="?attr/materialIconButtonStyle" - android:layout_width="wrap_content" + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="start" android:layout_margin="8dp" - app:icon="@drawable/ic_back" - app:iconSize="24dp" - app:iconTint="?attr/colorOnSurface" /> + android:orientation="horizontal"> + + <Button + android:id="@+id/button_back" + style="?attr/materialIconButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:icon="@drawable/ic_back" + app:iconSize="24dp" + app:iconTint="?attr/colorOnSurface" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <Button + android:id="@+id/button_shortcut" + style="?attr/materialIconButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:icon="@drawable/ic_shortcut" + app:iconSize="24dp" + app:iconTint="?attr/colorOnSurface" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> + + </androidx.constraintlayout.widget.ConstraintLayout> <com.google.android.material.card.MaterialCardView style="?attr/materialCardViewElevatedStyle" diff --git a/src/android/app/src/main/res/layout/fragment_game_properties.xml b/src/android/app/src/main/res/layout/fragment_game_properties.xml index 72ecbde30..cadd0bc4a 100644 --- a/src/android/app/src/main/res/layout/fragment_game_properties.xml +++ b/src/android/app/src/main/res/layout/fragment_game_properties.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout - xmlns:android="http://schemas.android.com/apk/res/android" +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" @@ -22,16 +21,35 @@ android:orientation="vertical" android:gravity="center_horizontal"> - <Button - android:id="@+id/button_back" - style="?attr/materialIconButtonStyle" - android:layout_width="wrap_content" + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="8dp" - android:layout_gravity="start" - app:icon="@drawable/ic_back" - app:iconSize="24dp" - app:iconTint="?attr/colorOnSurface" /> + android:orientation="horizontal"> + + <Button + android:id="@+id/button_back" + style="?attr/materialIconButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:icon="@drawable/ic_back" + app:iconSize="24dp" + app:iconTint="?attr/colorOnSurface" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <Button + android:id="@+id/button_shortcut" + style="?attr/materialIconButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:icon="@drawable/ic_shortcut" + app:iconSize="24dp" + app:iconTint="?attr/colorOnSurface" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + </androidx.constraintlayout.widget.ConstraintLayout> <com.google.android.material.card.MaterialCardView style="?attr/materialCardViewElevatedStyle" @@ -45,7 +63,7 @@ android:id="@+id/image_game_screen" android:layout_width="175dp" android:layout_height="175dp" - tools:src="@drawable/default_icon"/> + tools:src="@drawable/default_icon" /> </com.google.android.material.card.MaterialCardView> |