Add rating organizations information about rom

This commit is contained in:
kikimanjaro 2023-04-02 15:50:19 +02:00
parent d38cd3d58a
commit d59d5d560a
30 changed files with 202 additions and 21 deletions

View File

@ -12,7 +12,7 @@
#include "skyline/loader/nsp.h"
#include "skyline/jvm.h"
extern "C" JNIEXPORT jint JNICALL Java_emu_skyline_loader_RomFile_populate(JNIEnv *env, jobject thiz, jint jformat, jint fd, jstring appFilesPathJstring, jint systemLanguage) {
extern "C" JNIEXPORT jint JNICALL Java_emu_skyline_loader_RomFile_populate(JNIEnv *env, jobject thiz, jint jformat, jint fd, jstring appFilesPathJstring, jint systemLanguage, jint prefRatingOrganization) {
skyline::signal::ScopedStackBlocker stackBlocker;
skyline::loader::RomFormat format{static_cast<skyline::loader::RomFormat>(jformat)};
@ -55,6 +55,7 @@ extern "C" JNIEXPORT jint JNICALL Java_emu_skyline_loader_RomFile_populate(JNIEn
jfieldID applicationAuthorField{env->GetFieldID(clazz, "applicationAuthor", "Ljava/lang/String;")};
jfieldID rawIconField{env->GetFieldID(clazz, "rawIcon", "[B")};
jfieldID applicationVersionField{env->GetFieldID(clazz, "applicationVersion", "Ljava/lang/String;")};
jfieldID applicationRatingAgeField{env->GetFieldID(clazz, "applicationRatingAge", "Ljava/lang/String;")};
if (loader->nacp) {
auto language{skyline::language::GetApplicationLanguage(static_cast<skyline::language::SystemLanguage>(systemLanguage))};
@ -66,6 +67,10 @@ extern "C" JNIEXPORT jint JNICALL Java_emu_skyline_loader_RomFile_populate(JNIEn
env->SetObjectField(thiz, applicationTitleIdField, env->NewStringUTF(loader->nacp->GetSaveDataOwnerId().c_str()));
env->SetObjectField(thiz, applicationAuthorField, env->NewStringUTF(loader->nacp->GetApplicationPublisher(language).c_str()));
auto ratingOrganization{skyline::rating_age_organization::NacpRatingAgeOrganization(prefRatingOrganization)};
env->SetObjectField(thiz, applicationRatingAgeField, env->NewStringUTF(loader->nacp->GetRatingAge(ratingOrganization).c_str()));
auto icon{loader->GetIcon(language)};
jbyteArray iconByteArray{env->NewByteArray(static_cast<jsize>(icon.size()))};
env->SetByteArrayRegion(iconByteArray, 0, static_cast<jsize>(icon.size()), reinterpret_cast<const jbyte *>(icon.data()));

View File

@ -155,4 +155,47 @@ namespace skyline {
}
}
}
}
namespace rating_age_organization {
const int NacpRatingAgeOrganization_Count = 13;
/**
* @brief Returns the display name of the rating organization from the given index
*/
static const std::string ratingAgeOrganizationStrings[NacpRatingAgeOrganization_Count] = {
"CERO",
"GRACGCRB",
"GSRMR",
"ESRB",
"ClassInd",
"USK",
"PEGI",
"PEGIPortugal",
"PEGIBBFC",
"Russian",
"ACB",
"OFLC",
"IARCGeneric"
};
/**
* @brief An enum of all rating organizations and the associated index
*/
typedef enum {
NacpRatingAgeOrganization_CERO = 0,
NacpRatingAgeOrganization_GRACGCRB = 1,
NacpRatingAgeOrganization_GSRMR = 2,
NacpRatingAgeOrganization_ESRB = 3,
NacpRatingAgeOrganization_ClassInd = 4,
NacpRatingAgeOrganization_USK = 5,
NacpRatingAgeOrganization_PEGI = 6,
NacpRatingAgeOrganization_PEGIPortugal = 7,
NacpRatingAgeOrganization_PEGIBBFC = 8,
NacpRatingAgeOrganization_Russian = 9,
NacpRatingAgeOrganization_ACB = 10,
NacpRatingAgeOrganization_OFLC = 11,
NacpRatingAgeOrganization_IARCGeneric = 12,
} NacpRatingAgeOrganization;
}
}

View File

@ -43,4 +43,10 @@ namespace skyline::vfs {
auto applicationPublisher{span(nacpContents.titleEntries.at(static_cast<size_t>(language)).applicationPublisher)};
return std::string(applicationPublisher.as_string(true));
}
std::string NACP::GetRatingAge(rating_age_organization::NacpRatingAgeOrganization ratingAgeOrganization) {
int8_t age = *(((int8_t*)&(nacpContents.ratingAge)) + ratingAgeOrganization);
if (age < 0) age = 0;
return rating_age_organization::ratingAgeOrganizationStrings[ratingAgeOrganization] + " " + std::to_string(age);
}
}

View File

@ -23,11 +23,30 @@ namespace skyline::vfs {
static_assert(sizeof(ApplicationTitle) == 0x300);
public:
struct NacpRatingAge {
int8_t cero;
int8_t grac_gcrb;
int8_t gsrmr;
int8_t esrb;
int8_t class_ind;
int8_t usk;
int8_t pegi;
int8_t pegi_portugal;
int8_t pegi_bbfc;
int8_t russian;
int8_t acb;
int8_t oflc;
int8_t iarc_generic;
int8_t reserved[0x13];
};
static_assert(sizeof(NacpRatingAge) == 0x20);
struct NacpData {
std::array<ApplicationTitle, 0x10> titleEntries; //!< Title entries for each language
u8 _pad0_[0x2C];
u32 supportedLanguageFlag; //!< A bitmask containing the game's supported languages
u8 _pad1_[0x30];
u8 _pad5_[0x10];
NacpRatingAge ratingAge; //!< The rating age of the application
std::array<char, 0x10> displayVersion; //!< The user-readable version of the application
u8 _pad4_[0x8];
u64 saveDataOwnerId; //!< The ID that should be used for this application's savedata
@ -52,5 +71,7 @@ namespace skyline::vfs {
std::string GetSaveDataOwnerId();
std::string GetApplicationPublisher(language::ApplicationLanguage language);
std::string GetRatingAge(rating_age_organization::NacpRatingAgeOrganization ratingAgeOrganization);
};
}

View File

@ -66,6 +66,7 @@ class AppDialog : BottomSheetDialogFragment() {
binding.gameIcon.setImageBitmap(item.bitmapIcon)
binding.gameTitle.text = item.title
binding.gameVersion.text = item.version ?: item.loaderResultString(requireContext())
binding.gameRating.text = item.ratingAge ?: item.loaderResultString(requireContext())
binding.gameTitleId.text = item.titleId
binding.gameAuthor.text = item.author

View File

@ -247,7 +247,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
// The intent did not contain an app item, fall back to the data URI
val uri = intent.data!!
val romFormat = getRomFormat(uri, contentResolver)
val romFile = RomFile(this, romFormat, uri, EmulationSettings.global.systemLanguage)
val romFile = RomFile(this, romFormat, uri, EmulationSettings.global.systemLanguage, appSettings.ratingOrganization)
item = AppItem(romFile.takeIf { it.valid }!!.appEntry)
}

View File

@ -299,7 +299,7 @@ class MainActivity : AppCompatActivity() {
}
private fun loadRoms(loadFromFile : Boolean) {
viewModel.loadRoms(this, loadFromFile, Uri.parse(appSettings.searchLocation), EmulationSettings.global.systemLanguage)
viewModel.loadRoms(this, loadFromFile, Uri.parse(appSettings.searchLocation), EmulationSettings.global.systemLanguage, appSettings.ratingOrganization)
appSettings.refreshRequired = false
}

View File

@ -41,7 +41,7 @@ class MainViewModel @Inject constructor(@ApplicationContext context : Context, p
*
* @param loadFromFile If this is false then trying to load cached adapter data is skipped entirely
*/
fun loadRoms(context : Context, loadFromFile : Boolean, searchLocation : Uri, systemLanguage : Int) {
fun loadRoms(context : Context, loadFromFile : Boolean, searchLocation : Uri, systemLanguage : Int, ratingOrganization : Int) {
if (state == MainState.Loading) return
state = MainState.Loading
@ -63,7 +63,7 @@ class MainViewModel @Inject constructor(@ApplicationContext context : Context, p
} else {
try {
KeyReader.importFromLocation(context, searchLocation)
val romElements = romProvider.loadRoms(searchLocation, systemLanguage)
val romElements = romProvider.loadRoms(searchLocation, systemLanguage, ratingOrganization)
romElements.toFile(romsFile)
MainState.Loaded(romElements)
} catch (e : Exception) {

View File

@ -18,21 +18,21 @@ class RomProvider @Inject constructor(@ApplicationContext private val context :
* This adds all files in [directory] with [extension] as an entry using [RomFile] to load metadata
*/
@SuppressLint("DefaultLocale")
private fun addEntries(fileFormats : Map<String, RomFormat>, directory : DocumentFile, entries : HashMap<RomFormat, ArrayList<AppEntry>>, systemLanguage : Int) {
private fun addEntries(fileFormats : Map<String, RomFormat>, directory : DocumentFile, entries : HashMap<RomFormat, ArrayList<AppEntry>>, systemLanguage : Int, ratingOrganization : Int) {
directory.listFiles().forEach { file ->
if (file.isDirectory) {
addEntries(fileFormats, file, entries, systemLanguage)
addEntries(fileFormats, file, entries, systemLanguage, ratingOrganization)
} else {
fileFormats[file.name?.substringAfterLast(".")?.lowercase()]?.let { romFormat->
entries.getOrPut(romFormat, { arrayListOf() }).add(RomFile(context, romFormat, file.uri, systemLanguage).appEntry)
entries.getOrPut(romFormat, { arrayListOf() }).add(RomFile(context, romFormat, file.uri, systemLanguage, ratingOrganization).appEntry)
}
}
}
}
fun loadRoms(searchLocation : Uri, systemLanguage : Int) = DocumentFile.fromTreeUri(context, searchLocation)!!.let { documentFile ->
fun loadRoms(searchLocation : Uri, systemLanguage : Int, ratingOrganization : Int) = DocumentFile.fromTreeUri(context, searchLocation)!!.let { documentFile ->
hashMapOf<RomFormat, ArrayList<AppEntry>>().apply {
addEntries(mapOf("nro" to NRO, "nso" to NSO, "nca" to NCA, "nsp" to NSP, "xci" to XCI), documentFile, this, systemLanguage)
addEntries(mapOf("nro" to NRO, "nso" to NSO, "nca" to NCA, "nsp" to NSP, "xci" to XCI), documentFile, this, systemLanguage, ratingOrganization)
}
}
}

View File

@ -46,6 +46,8 @@ interface LayoutBinding<V : ViewBinding> : ViewBinding {
val textVersion : TextView
val ratingAge : TextView
val textAuthor : TextView
val icon : ImageView
@ -58,6 +60,8 @@ class ListBinding(parent : ViewGroup) : LayoutBinding<AppItemLinearBinding> {
override val textVersion = binding.textVersion
override val ratingAge = binding.textVersion
override val textAuthor = binding.textAuthor
override val icon = binding.icon
@ -70,6 +74,8 @@ class GridBinding(parent : ViewGroup) : LayoutBinding<AppItemGridBinding> {
override val textVersion = binding.textVersion
override val ratingAge = binding.textVersion
override val textAuthor = binding.textAuthor
override val icon = binding.icon
@ -82,6 +88,8 @@ class GridCompatBinding(parent : ViewGroup) : LayoutBinding<AppItemGridCompactBi
override val textVersion = binding.textVersion
override val ratingAge = binding.textVersion
override val textAuthor = binding.textAuthor
override val icon = binding.icon

View File

@ -53,6 +53,11 @@ data class AppItem(private val meta : AppEntry) : DataItem() {
*/
val version get() = meta.version
/**
* The application rating age
*/
val ratingAge get() = meta.ratingAge
/**
* The application author
*/

View File

@ -66,6 +66,7 @@ enum class LoaderResult(val value : Int) {
data class AppEntry(
var name : String,
var version : String?,
var ratingAge : String?,
var titleId : String?,
var author : String?,
var icon : Bitmap?,
@ -77,7 +78,7 @@ data class AppEntry(
val nameIndex : Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
cursor.getString(nameIndex)
}!!.dropLast(format.name.length + 1), null, null, null, null, format, uri, loaderResult)
}!!.dropLast(format.name.length + 1), null, null, null, null, null, format, uri, loaderResult)
private fun writeObject(output : ObjectOutputStream) {
output.writeUTF(name)
@ -86,6 +87,9 @@ data class AppEntry(
output.writeBoolean(version != null)
if (version != null)
output.writeUTF(version)
output.writeBoolean(ratingAge != null)
if (ratingAge != null)
output.writeUTF(ratingAge)
output.writeBoolean(titleId != null)
if (titleId != null)
output.writeUTF(titleId)
@ -109,6 +113,8 @@ data class AppEntry(
uri = Uri.parse(input.readUTF())
if (input.readBoolean())
version = input.readUTF()
if (input.readBoolean())
ratingAge = input.readUTF()
if (input.readBoolean())
titleId = input.readUTF()
if (input.readBoolean())
@ -129,7 +135,7 @@ data class AppEntry(
/**
* This class is used as interface between libskyline and Kotlin for loaders
*/
internal class RomFile(context : Context, format : RomFormat, uri : Uri, systemLanguage : Int) {
internal class RomFile(context : Context, format : RomFormat, uri : Uri, systemLanguage : Int, ratingOrganization : Int) {
/**
* @note This field is filled in by native code
*/
@ -145,6 +151,11 @@ internal class RomFile(context : Context, format : RomFormat, uri : Uri, systemL
*/
private var applicationVersion : String? = null
/**
* @note This field is filled in by native code
*/
private var applicationRatingAge : String? = null
/**
* @note This field is filled in by native code
*/
@ -164,15 +175,17 @@ internal class RomFile(context : Context, format : RomFormat, uri : Uri, systemL
init {
context.contentResolver.openFileDescriptor(uri, "r")!!.use {
result = LoaderResult.get(populate(format.ordinal, it.fd, "${context.filesDir.canonicalPath}/keys/", systemLanguage))
result = LoaderResult.get(populate(format.ordinal, it.fd, "${context.filesDir.canonicalPath}/keys/", systemLanguage, ratingOrganization))
}
appEntry = applicationName?.let { name ->
applicationVersion?.let { version ->
applicationTitleId?.let { titleId ->
applicationAuthor?.let { author ->
rawIcon?.let { icon ->
AppEntry(name, version, titleId, author, BitmapFactory.decodeByteArray(icon, 0, icon.size), format, uri, result)
applicationRatingAge?.let { ratingAge ->
applicationTitleId?.let { titleId ->
applicationAuthor?.let { author ->
rawIcon?.let { icon ->
AppEntry(name, version, ratingAge, titleId, author, BitmapFactory.decodeByteArray(icon, 0, icon.size), format, uri, result)
}
}
}
}
@ -187,5 +200,5 @@ internal class RomFile(context : Context, format : RomFormat, uri : Uri, systemL
* @param appFilesPath Path to internal app data storage, needed to read imported keys
* @return A pointer to the newly allocated object, or 0 if the ROM is invalid
*/
private external fun populate(format : Int, romFd : Int, appFilesPath : String, systemLanguage : Int) : Int
private external fun populate(format : Int, romFd : Int, appFilesPath : String, systemLanguage : Int, prefRatingOrganization : Int) : Int
}

View File

@ -0,0 +1,48 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.preference
import android.content.Context
import android.util.AttributeSet
import androidx.preference.ListPreference
import androidx.preference.Preference.OnPreferenceChangeListener
import androidx.preference.Preference.SummaryProvider
import emu.skyline.di.getSettings
class RatingPreference @JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defStyleAttr : Int = androidx.preference.R.attr.preferenceStyle) : ListPreference(context, attrs, defStyleAttr) {
val RATING_ORGANIZATIONS = arrayOf(
"CERO",
"GRACGCRB",
"GSRMR",
"ESRB",
"ClassInd",
"USK",
"PEGI",
"PEGIPortugal",
"PEGIBBFC",
"Russian",
"ACB",
"OFLC",
"IARCGeneric"
)
init {
entries = RATING_ORGANIZATIONS
entryValues = RATING_ORGANIZATIONS.map { RATING_ORGANIZATIONS.indexOf(it).toString() }.toTypedArray()
value = context?.getSettings()?.ratingOrganization.toString()
onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
context?.getSettings()?.ratingOrganization = (newValue as String).toInt()
context?.getSettings()?.refreshRequired = true
true
}
summaryProvider = SummaryProvider<ListPreference> {
RATING_ORGANIZATIONS[context?.getSettings()?.ratingOrganization!!]
}
}
}

View File

@ -26,6 +26,7 @@ class AppSettings @Inject constructor(@ApplicationContext private val context :
var sortAppsBy by sharedPreferences(context, 0)
var groupByFormat by sharedPreferences(context, true)
var selectAction by sharedPreferences(context, false)
var ratingOrganization by sharedPreferences(context, 3)
// Input
var onScreenControl by sharedPreferences(context, true)

View File

@ -73,12 +73,24 @@
tools:text="1.0.0" />
<TextView
android:id="@+id/game_author"
android:id="@+id/game_rating"
style="?attr/textAppearanceBodyMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@id/game_version"
app:layout_constraintTop_toBottomOf="@id/game_version"
tools:text="" />
<TextView
android:id="@+id/game_author"
style="?attr/textAppearanceBodyMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="@android:color/tertiary_text_light"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="@id/game_rating"
app:layout_constraintTop_toBottomOf="@id/game_rating"
tools:text="Nintendo" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -42,6 +42,7 @@
<string name="theme">Tema</string>
<string name="app_language">Idioma de la aplicación</string>
<string name="app_language_default">Usar idioma del sistema</string>
<string name="rating_organization">Clasificación Organización</string>
<string name="layout_type">Tipo de vista de juegos</string>
<string name="sort_apps_by">Ordenar juegos por</string>
<string name="group_by_format">Agrupar juegos por formato</string>

View File

@ -48,6 +48,7 @@
<string name="gpu_driver_config_desc_unsupported">Dein Gerät unterstützt keine anderen Treiber</string>
<string name="app_language">App-Sprache</string>
<string name="app_language_default">Verwende Systemsprache</string>
<string name="rating_organization">Bewertung Organisation</string>
<!-- Settings - System -->
<string name="system">System</string>
<string name="use_docked">Verwende Docked Modus</string>

View File

@ -48,6 +48,7 @@
<string name="gpu_driver_config_desc_unsupported">Η συσκευή σου δεν υποστηρίζει προσαρμοσμένους οδηγούς</string>
<string name="app_language">Γλώσσα εφαρμογής</string>
<string name="app_language_default">Χρησιμοποιήστε την προεπιλογή συστήματος</string>
<string name="rating_organization">Οργανισμός αξιολόγησης</string>
<!-- Settings - System -->
<string name="system">Σύστημα</string>
<string name="use_docked">Χρησιμοποίησε λειτουργία σύνδεσης</string>

View File

@ -48,6 +48,7 @@
<string name="gpu_driver_config_desc_unsupported">Tu dispositivo no soporta drivers personalizados </string>
<string name="app_language">Idioma de la aplicación</string>
<string name="app_language_default">Usar idioma del sistema</string>
<string name="rating_organization">Clasificación Organización</string>
<!-- Settings - System -->
<string name="system">Sistema</string>
<string name="use_docked">Usar Modo Dock</string>

View File

@ -42,6 +42,7 @@
<string name="theme">Thème</string>
<string name="app_language">Langue de l\'application</string>
<string name="app_language_default">Utiliser Langue Système</string>
<string name="rating_organization">Organisme de Classification</string>
<string name="layout_type">Mode d\'affichage des jeux</string>
<string name="sort_apps_by">Ordre de tri des jeux</string>
<string name="group_by_format">Grouper les jeux par format</string>

View File

@ -48,6 +48,7 @@
<string name="gpu_driver_config_desc_unsupported">Az eszközöd nem támogatja az egyedi illesztőprogramokat</string>
<string name="app_language">Alkalmazás nyelve</string>
<string name="app_language_default">A rendszer alapértelmezés használata</string>
<string name="rating_organization">Értékelő szervezet</string>
<!-- Settings - System -->
<string name="system">Rendszer</string>
<string name="use_docked">Dokkolt mód használata</string>

View File

@ -48,6 +48,7 @@
<string name="gpu_driver_config_desc_unsupported">Perangkat kamu tidak mendukung custom driver</string>
<string name="app_language">Bahasa Aplikasi</string>
<string name="app_language_default">Gunakan Bawaan Sistem</string>
<string name="rating_organization">Organisasi Pemeringkatan</string>
<!-- Settings - System -->
<string name="system">Sistem</string>
<string name="use_docked">Gunakan Mode Docked</string>

View File

@ -48,6 +48,7 @@
<string name="gpu_driver_config_desc_unsupported">Questo dispositivo non supporta i driver personalizzati</string>
<string name="app_language">Lingua App</string>
<string name="app_language_default">Usa Predefinito di Sistema</string>
<string name="rating_organization">Valutazione Organizzazione</string>
<!-- Settings - System -->
<string name="system">Sistema</string>
<string name="use_docked">Utilizza la modalità console fissa</string>

View File

@ -48,6 +48,7 @@
<string name="gpu_driver_config_desc_unsupported">장치가 사용자 정의 드라이버를 지원하지 않습니다</string>
<string name="app_language">앱 언어</string>
<string name="app_language_default">시스템 기본값 사용</string>
<string name="rating_organization">평가 조직</string>
<!-- Settings - System -->
<string name="system">체계</string>
<string name="use_docked">도킹 모드 사용</string>

View File

@ -48,6 +48,7 @@
<string name="gpu_driver_config_desc_unsupported">Twoje urządzenie nie wspiera wymiennych sterowników</string>
<string name="app_language">Język aplikacji</string>
<string name="app_language_default">Użyj ustawienia domyślne systemu</string>
<string name="rating_organization">Ocena Organizacja</string>
<!-- Settings - System -->
<string name="system">System</string>
<string name="use_docked">Użyj trybu Dokowania</string>

View File

@ -42,6 +42,7 @@
<string name="theme">Tema</string>
<string name="app_language">Idioma do Aplicativo</string>
<string name="app_language_default">Usar Padrão do Sistema</string>
<string name="rating_organization">Organização de classificação</string>
<string name="layout_type">Modo de Exibição dos Jogos</string>
<string name="sort_apps_by">Ordem de Classificação dos Jogos</string>
<string name="group_by_format">Agrupar Jogos por Formato</string>

View File

@ -42,6 +42,7 @@
<string name="theme">Tema</string>
<string name="app_language">Idioma do Aplicativo</string>
<string name="app_language_default">Usar padrão do sistema</string>
<string name="rating_organization">Organização de classificação</string>
<string name="layout_type">Modo de Exibição dos Jogos</string>
<string name="sort_apps_by">Ordem de Classificação dos Jogos</string>
<string name="group_by_format">Agrupar Jogos por Formato</string>

View File

@ -48,6 +48,7 @@
<string name="gpu_driver_config_desc_unsupported">Ваше устройство не поддерживает пользовательские драйвера</string>
<string name="app_language">Язык приложения</string>
<string name="app_language_default">Использовать значение системы</string>
<string name="rating_organization">Рейтинговая организация</string>
<!-- Settings - System -->
<string name="system">Система</string>
<string name="use_docked">Использовать режим док-станции</string>

View File

@ -49,6 +49,7 @@
<string name="use_material_you_desc_on">Material You will be used</string>
<string name="app_language">App Language</string>
<string name="app_language_default">Use System Default</string>
<string name="rating_organization">Rating Organization</string>
<string name="layout_type">Game Display Layout</string>
<string name="sort_apps_by">Games Sorting Order</string>
<string name="group_by_format">Group Games By Format</string>

View File

@ -38,6 +38,10 @@
app:key="app_language"
app:persistent="false"
app:title="@string/app_language" />
<emu.skyline.preference.RatingPreference
app:key="rating_organization"
app:persistent="false"
app:title="@string/rating_organization" />
<emu.skyline.preference.IntegerListPreference
android:defaultValue="1"
android:entries="@array/layout_type"