Compare commits
8 commits
87920737eb
...
e6face74c6
Author | SHA1 | Date | |
---|---|---|---|
e6face74c6 | |||
43ac4adbb7 | |||
3348312d2a | |||
3ecd929a18 | |||
87c2579b63 | |||
19eb8272b1 | |||
86ddb51a87 | |||
10aca2f90c |
8 changed files with 136 additions and 7 deletions
|
@ -27,6 +27,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" android:required="false" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
|
||||
|
||||
|
@ -93,6 +95,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
<meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" />
|
||||
</activity>
|
||||
|
||||
<service android:name="org.yuzu.yuzu_emu.utils.ForegroundService" android:foregroundServiceType="specialUse">
|
||||
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="Keep emulation running in background"/>
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name=".features.DocumentProvider"
|
||||
android:authorities="${applicationId}.user"
|
||||
|
|
|
@ -22,6 +22,17 @@ fun Context.getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir
|
|||
|
||||
class YuzuApplication : Application() {
|
||||
private fun createNotificationChannels() {
|
||||
val name: CharSequence = getString(R.string.app_notification_channel_name)
|
||||
val description = getString(R.string.app_notification_channel_description)
|
||||
val foregroundService = NotificationChannel(
|
||||
getString(R.string.app_notification_channel_id),
|
||||
name,
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
)
|
||||
foregroundService.description = description
|
||||
foregroundService.setSound(null, null)
|
||||
foregroundService.vibrationPattern = null
|
||||
|
||||
val noticeChannel = NotificationChannel(
|
||||
getString(R.string.notice_notification_channel_id),
|
||||
getString(R.string.notice_notification_channel_name),
|
||||
|
@ -34,6 +45,7 @@ class YuzuApplication : Application() {
|
|||
// or other notification behaviors after this
|
||||
val notificationManager = getSystemService(NotificationManager::class.java)
|
||||
notificationManager.createNotificationChannel(noticeChannel)
|
||||
notificationManager.createNotificationChannel(foregroundService)
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
package org.yuzu.yuzu_emu.activities
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.PendingIntent
|
||||
import android.app.PictureInPictureParams
|
||||
import android.app.RemoteAction
|
||||
|
@ -59,6 +60,7 @@ import org.yuzu.yuzu_emu.utils.ParamPackage
|
|||
import org.yuzu.yuzu_emu.utils.ThemeHelper
|
||||
import java.text.NumberFormat
|
||||
import kotlin.math.roundToInt
|
||||
import org.yuzu.yuzu_emu.utils.ForegroundService
|
||||
|
||||
class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
private lateinit var binding: ActivityEmulationBinding
|
||||
|
@ -78,6 +80,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
|
||||
private val emulationViewModel: EmulationViewModel by viewModels()
|
||||
|
||||
private var foregroundService: Intent? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
Log.gameLaunched = true
|
||||
ThemeHelper.setTheme(this)
|
||||
|
@ -128,6 +132,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
nfcReader = NfcReader(this)
|
||||
nfcReader.initialize()
|
||||
|
||||
foregroundService = Intent(this, ForegroundService::class.java)
|
||||
startForegroundService(foregroundService)
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) {
|
||||
if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.totalMemory)) {
|
||||
|
@ -189,6 +196,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
stopMotionSensorListener()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
stopForegroundService(this)
|
||||
|
||||
}
|
||||
|
||||
override fun onUserLeaveHint() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||
if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) {
|
||||
|
@ -511,6 +524,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
companion object {
|
||||
const val EXTRA_SELECTED_GAME = "SelectedGame"
|
||||
|
||||
fun stopForegroundService(activity: Activity) {
|
||||
val startIntent = Intent(activity, ForegroundService::class.java)
|
||||
startIntent.action = ForegroundService.ACTION_STOP
|
||||
activity.startForegroundService(startIntent)
|
||||
}
|
||||
|
||||
fun launch(activity: AppCompatActivity, game: Game) {
|
||||
val launcher = Intent(activity, EmulationActivity::class.java)
|
||||
launcher.putExtra(EXTRA_SELECTED_GAME, game)
|
||||
|
|
|
@ -48,6 +48,7 @@ import java.io.BufferedOutputStream
|
|||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import androidx.core.content.edit
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||
import kotlin.text.compareTo
|
||||
|
||||
class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
@ -188,6 +189,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
if (it) checkKeys()
|
||||
}
|
||||
|
||||
// Dismiss previous notifications (should not happen unless a crash occurred)
|
||||
EmulationActivity.stopForegroundService(this)
|
||||
|
||||
setInsets()
|
||||
}
|
||||
|
||||
|
@ -293,6 +297,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
themeId = resId
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
EmulationActivity.stopForegroundService(this)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
val getGamesDirectory =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
|
||||
if (result != null) {
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||
|
||||
/**
|
||||
* A service that shows a permanent notification in the background to avoid the app getting
|
||||
* cleared from memory by the system.
|
||||
*/
|
||||
class ForegroundService : Service() {
|
||||
companion object {
|
||||
const val EMULATION_RUNNING_NOTIFICATION = 0x1000
|
||||
|
||||
const val ACTION_STOP = "stop"
|
||||
}
|
||||
|
||||
private fun showRunningNotification() {
|
||||
// Intent is used to resume emulation if the notification is clicked
|
||||
val contentIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
Intent(this, EmulationActivity::class.java),
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
val builder =
|
||||
NotificationCompat.Builder(this, getString(R.string.app_notification_channel_id))
|
||||
.setSmallIcon(R.drawable.ic_stat_notification_logo)
|
||||
.setContentTitle(getString(R.string.app_name))
|
||||
.setContentText(getString(R.string.app_notification_running))
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setOngoing(true)
|
||||
.setVibrate(null)
|
||||
.setSound(null)
|
||||
.setContentIntent(contentIntent)
|
||||
startForeground(EMULATION_RUNNING_NOTIFICATION, builder.build())
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
showRunningNotification()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (intent?.action == ACTION_STOP) {
|
||||
try {
|
||||
NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION)
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
} catch (e: Exception) {
|
||||
Log.error("Failed to stop foreground service")
|
||||
}
|
||||
stopSelfResult(startId)
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
if (intent != null) {
|
||||
showRunningNotification()
|
||||
}
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() =
|
||||
NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION)
|
||||
}
|
|
@ -8,6 +8,11 @@
|
|||
<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>
|
||||
<string name="app_notification_channel_name" translatable="false">Eden</string>
|
||||
<string name="app_notification_channel_id" translatable="false">Eden</string>
|
||||
<string name="app_notification_channel_description">Eden Switch emulator notifications</string>
|
||||
<string name="app_notification_running">Eden is Running</string>
|
||||
|
||||
|
||||
<!-- Stats Overlay settings -->
|
||||
<string name="enhanced_fps_suffix">(Enhanced)</string>
|
||||
|
|
|
@ -792,6 +792,11 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
|
|||
const u32 size = (std::min)(binding.size, (*channel_state->uniform_buffer_sizes)[stage][index]);
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
TouchBuffer(buffer, binding.buffer_id);
|
||||
const bool sync_buffer = SynchronizeBuffer(buffer, device_addr, size);
|
||||
if (sync_buffer) {
|
||||
++channel_state->uniform_cache_hits[0];
|
||||
}
|
||||
++channel_state->uniform_cache_shots[0];
|
||||
const bool use_fast_buffer = binding.buffer_id != NULL_BUFFER_ID &&
|
||||
size <= channel_state->uniform_buffer_skip_cache_size &&
|
||||
!memory_tracker.IsRegionGpuModified(device_addr, size);
|
||||
|
@ -822,12 +827,6 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
|
|||
device_memory.ReadBlockUnsafe(device_addr, span.data(), size);
|
||||
return;
|
||||
}
|
||||
// Classic cached path
|
||||
const bool sync_cached = SynchronizeBuffer(buffer, device_addr, size);
|
||||
if (sync_cached) {
|
||||
++channel_state->uniform_cache_hits[0];
|
||||
}
|
||||
++channel_state->uniform_cache_shots[0];
|
||||
|
||||
// Skip binding if it's not needed and if the bound buffer is not the fast version
|
||||
// This exists to avoid instances where the fast buffer is bound and a GPU write happens
|
||||
|
|
|
@ -31,7 +31,7 @@ struct DescriptorBank {
|
|||
bool DescriptorBankInfo::IsSuperset(const DescriptorBankInfo& subset) const noexcept {
|
||||
return uniform_buffers >= subset.uniform_buffers && storage_buffers >= subset.storage_buffers &&
|
||||
texture_buffers >= subset.texture_buffers && image_buffers >= subset.image_buffers &&
|
||||
textures >= subset.textures && images >= subset.image_buffers;
|
||||
textures >= subset.textures && images >= subset.images;
|
||||
}
|
||||
|
||||
template <typename Descriptors>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue