From 80c0f8f04d071999899cc9c8e240cdc2de75a653 Mon Sep 17 00:00:00 2001 From: PabloG02 <71378035+PabloG02@users.noreply.github.com> Date: Tue, 27 Dec 2022 18:23:41 +0100 Subject: [PATCH] Implement full profile picture support Extends the profile picture stub into a full-fledged implementation with the ability for users to set their profile picture in settings while having the Skyline icon as the default profile picture. --- app/build.gradle | 1 + app/src/main/assets/profile_picture.jpeg | Bin 0 -> 19272 bytes .../cpp/skyline/common/android_settings.h | 1 + app/src/main/cpp/skyline/common/settings.h | 1 + .../cpp/skyline/services/account/IProfile.cpp | 38 ++++---- .../cpp/skyline/services/account/IProfile.h | 6 ++ .../preference/ProfilePicturePreference.kt | 83 ++++++++++++++++++ .../java/emu/skyline/utils/NativeSettings.kt | 1 + .../emu/skyline/utils/PreferenceSettings.kt | 1 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/preferences.xml | 3 + 11 files changed, 119 insertions(+), 17 deletions(-) create mode 100644 app/src/main/assets/profile_picture.jpeg create mode 100644 app/src/main/java/emu/skyline/preference/ProfilePicturePreference.kt diff --git a/app/build.gradle b/app/build.gradle index f364e302..fb114408 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -153,6 +153,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.preference:preference-ktx:1.2.0' + implementation 'androidx.activity:activity-ktx:1.6.1' implementation 'com.google.android.material:material:1.6.1' implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' diff --git a/app/src/main/assets/profile_picture.jpeg b/app/src/main/assets/profile_picture.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..b1c38ce5bf8f45691acb0e566c0a43a53f522eb0 GIT binary patch literal 19272 zcmd42XH-+&_bwVlMFm8pNsWqvbU}K{iwFn^5s;3cB2r?A^cIp=5D=tDS6ZZo9(oN# zx`Gfo(n%1Io=^jXaPzz8+4_8q`}i2=X>U}U%rILFSw$j)%q2>=2B z3>W^}*nt0jFq~sNf8iq2rOQ{C=^bjX0nRZnGM+onc;UkN^Yq@q^zQ-Z*)LqbC98Fj z!{jB?Z4XYl_wm`6gdbM6ahVR0?mT$q8FKjwH_r`TzPlo#_r%2I6%>_}RaCVf>FDY` z);BPFW^Q3=Wo_g5+Ubq6i>sTLw~w!%e?Va9hp_O7kC9OciAl*Rsb9XP<>cn&7oZD^ zimR$?YU}D78k^cXI=i}ie*Ekm9vK}QpZGmFMIbKzSz2ECyShf++1=YeIHVjM|HrQX z*!h1Q|IM)f!7g^XUFXi9XFSjJAG;XN`OzCA`}qsEWG`OVGGTh@!Esye{Uy$a@!6Ga zmxUjglDJ-Z4qf5CBTu+X{*P(@r)B?ZhK2lpvh4ph?EkTA3c$k1K>za?*#RH`P55t4 z6yVFmo8tjarg49%a1&A>$MiS%7f@_<5-fD$Kg+*5 zv>Ijc(#mmJl{cNA(UY$-grKe6ln#)iZ0)dr=UG6`Yz%RisPcoiMjTyk(ihf(M+FQ%gnsM5_*tq%yP%07CAw#(fj;@sm z3f3mB#CgwOiahsIG(ug%tyOp|9y}MaYtxW6qFdGYkYA)7c+FPv;^dDzap(=?LK?E# zKJi>nkW8l_;zh3aacbly^y@TGmi_OPX6kim#9Hzzo^U^q?5RL&dY{?P1%ZrcTIf7x zyzvHMOkvidTA2gpeEYc?r6Y zp6Z*}sbpEEIC;;wz8urv?6+>JnyRaXkSrxrRjbgI#@F�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 uQ#yg%2Ej literal 0 HcmV?d00001 diff --git a/app/src/main/cpp/skyline/common/android_settings.h b/app/src/main/cpp/skyline/common/android_settings.h index eedbd7c4..77f4ad0d 100644 --- a/app/src/main/cpp/skyline/common/android_settings.h +++ b/app/src/main/cpp/skyline/common/android_settings.h @@ -33,6 +33,7 @@ namespace skyline { void Update() override { isDocked = ktSettings.GetBool("isDocked"); usernameValue = std::move(ktSettings.GetString("usernameValue")); + profilePictureValue = ktSettings.GetString("profilePictureValue"); systemLanguage = ktSettings.GetInt("systemLanguage"); systemRegion = ktSettings.GetInt("systemRegion"); forceTripleBuffering = ktSettings.GetBool("forceTripleBuffering"); diff --git a/app/src/main/cpp/skyline/common/settings.h b/app/src/main/cpp/skyline/common/settings.h index 47cc2ca4..ef181d1d 100644 --- a/app/src/main/cpp/skyline/common/settings.h +++ b/app/src/main/cpp/skyline/common/settings.h @@ -61,6 +61,7 @@ namespace skyline { // System Setting isDocked; //!< If the emulated Switch should be handheld or docked Setting usernameValue; //!< The user name to be supplied to the guest + Setting profilePictureValue; //!< The profile picture path to be supplied to the guest Setting systemLanguage; //!< The system language Setting systemRegion; //!< The system region diff --git a/app/src/main/cpp/skyline/services/account/IProfile.cpp b/app/src/main/cpp/skyline/services/account/IProfile.cpp index 13ae1358..a966d36d 100644 --- a/app/src/main/cpp/skyline/services/account/IProfile.cpp +++ b/app/src/main/cpp/skyline/services/account/IProfile.cpp @@ -1,23 +1,13 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include +#include +#include #include #include "IProfile.h" namespace skyline::service::account { - // Smallest JPEG file https://github.com/mathiasbynens/small/blob/master/jpeg.jpg - constexpr std::array profileImageIcon{ - 0xFF, 0xD8, 0xFF, 0xDB, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, - 0x02, 0x03, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, - 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, 0x06, 0x09, 0x08, 0x0A, - 0x0A, 0x09, 0x08, 0x09, 0x09, 0x0A, 0x0C, 0x0F, 0x0C, 0x0A, 0x0B, 0x0E, - 0x0B, 0x09, 0x09, 0x0D, 0x11, 0x0D, 0x0E, 0x0F, 0x10, 0x10, 0x11, 0x10, - 0x0A, 0x0C, 0x12, 0x13, 0x12, 0x10, 0x13, 0x0F, 0x10, 0x10, 0x10, 0xFF, - 0xC9, 0x00, 0x0B, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x11, 0x00, - 0xFF, 0xCC, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xFF, 0xDA, 0x00, 0x08, - 0x01, 0x01, 0x00, 0x00, 0x3F, 0x00, 0xD2, 0xCF, 0x20, 0xFF, 0xD9 - }; - IProfile::IProfile(const DeviceState &state, ServiceManager &manager, const UserId &userId) : userId(userId), BaseService(state, manager) {} Result IProfile::Get(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { @@ -53,14 +43,28 @@ namespace skyline::service::account { } Result IProfile::GetImageSize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - response.Push(profileImageIcon.size()); + std::shared_ptr profileImageIcon{GetProfilePicture()}; + response.Push(static_cast(profileImageIcon->size)); + return {}; } Result IProfile::LoadImage(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - // TODO: load actual profile image - request.outputBuf.at(0).copy_from(profileImageIcon); - response.Push(profileImageIcon.size()); + std::shared_ptr profileImageIcon{GetProfilePicture()}; + + profileImageIcon->Read(span(request.outputBuf.at(0)).first(profileImageIcon->size), 0); + response.Push(static_cast(profileImageIcon->size)); + return {}; } + + std::shared_ptr IProfile::GetProfilePicture() { + std::string profilePicturePath{*state.settings->profilePictureValue}; + int fd{open((profilePicturePath).c_str(), O_RDONLY)}; + if (fd < 0) + // If we can't find the profile picture then just return the placeholder profile picture + return state.os->assetFileSystem->OpenFile("profile_picture.jpeg"); + else + return std::make_shared(fd, true); + } } diff --git a/app/src/main/cpp/skyline/services/account/IProfile.h b/app/src/main/cpp/skyline/services/account/IProfile.h index 006e775b..ee8a6205 100644 --- a/app/src/main/cpp/skyline/services/account/IProfile.h +++ b/app/src/main/cpp/skyline/services/account/IProfile.h @@ -38,6 +38,12 @@ namespace skyline::service::account { */ Result LoadImage(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + /** + * @brief Tries to get the user's profile picture. If not found, returns the default one + * @return A shared pointer to a Backing object of the profile picture + */ + std::shared_ptr GetProfilePicture(); + SERVICE_DECL( SFUNC(0x0, IProfile, Get), SFUNC(0x1, IProfile, GetBase), diff --git a/app/src/main/java/emu/skyline/preference/ProfilePicturePreference.kt b/app/src/main/java/emu/skyline/preference/ProfilePicturePreference.kt new file mode 100644 index 00000000..bb86f6c3 --- /dev/null +++ b/app/src/main/java/emu/skyline/preference/ProfilePicturePreference.kt @@ -0,0 +1,83 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + */ + +package emu.skyline.preference + +import android.content.ContentResolver +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import android.util.AttributeSet +import androidx.activity.ComponentActivity +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.preference.Preference +import androidx.preference.Preference.SummaryProvider +import androidx.preference.PreferenceManager +import androidx.preference.R +import emu.skyline.SkylineApplication +import emu.skyline.getPublicFilesDir +import java.io.File +import java.io.FileOutputStream +import java.io.InputStream + +class ProfilePicturePreference @JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defStyleAttr : Int = R.attr.preferenceStyle) : Preference(context, attrs, defStyleAttr) { + private val pickMedia = (context as ComponentActivity).registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> + val profilePictureDir = SkylineApplication.instance.getPublicFilesDir().canonicalPath + "/switch/nand/system/save/8000000000000010/su/avators" + val profilePictureName = "profile_picture.jpeg" + try { + if (uri != null) { // The user selected a picture + PreferenceManager.getDefaultSharedPreferences(context).edit().putString(key, "$profilePictureDir/$profilePictureName").apply() + File(profilePictureDir).mkdirs() + context.applicationContext.contentResolver.let { contentResolver : ContentResolver -> + val readUriPermission : Int = Intent.FLAG_GRANT_READ_URI_PERMISSION + contentResolver.takePersistableUriPermission(uri, readUriPermission) + contentResolver.openInputStream(uri)?.use { inputStream : InputStream -> + var bitmap = BitmapFactory.decodeStream(inputStream) + // Compress the picture + bitmap = Bitmap.createScaledBitmap(bitmap, 256, 256, false) + StoreBitmap(bitmap, "$profilePictureDir/$profilePictureName") + } + } + } else { // No picture was selected, clear the profile picture if one was already set + if (File("$profilePictureDir/$profilePictureName").exists()) { + File("$profilePictureDir/$profilePictureName").delete() + } + PreferenceManager.getDefaultSharedPreferences(context).edit().putString(key, "No picture selected").apply() + } + notifyChanged() + } catch (e : Exception) { + e.printStackTrace() + } + } + + init { + summaryProvider = SummaryProvider { preference -> + Uri.decode(preference.getPersistedString("No picture selected")) + } + } + + override fun onClick() = pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) + + /** + * Given a bitmap, saves it in the specified location + */ + private fun StoreBitmap(bitmap : Bitmap, filePath : String) { + try { + // Create the file where the bitmap will be stored + val file = File(filePath) + file.createNewFile() + // Store bitmap as JPEG + val outputFile = FileOutputStream(file) + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputFile) + outputFile.flush() + outputFile.close() + } catch (e : Exception) { + e.printStackTrace() + } + } +} diff --git a/app/src/main/java/emu/skyline/utils/NativeSettings.kt b/app/src/main/java/emu/skyline/utils/NativeSettings.kt index 39ce55da..5d7777f7 100644 --- a/app/src/main/java/emu/skyline/utils/NativeSettings.kt +++ b/app/src/main/java/emu/skyline/utils/NativeSettings.kt @@ -15,6 +15,7 @@ class NativeSettings(context : Context, pref : PreferenceSettings) { // System var isDocked : Boolean = pref.isDocked var usernameValue : String = pref.usernameValue + var profilePictureValue : String = pref.profilePictureValue var systemLanguage : Int = pref.systemLanguage var systemRegion : Int = pref.systemRegion diff --git a/app/src/main/java/emu/skyline/utils/PreferenceSettings.kt b/app/src/main/java/emu/skyline/utils/PreferenceSettings.kt index 611a635f..812aa2ba 100644 --- a/app/src/main/java/emu/skyline/utils/PreferenceSettings.kt +++ b/app/src/main/java/emu/skyline/utils/PreferenceSettings.kt @@ -25,6 +25,7 @@ class PreferenceSettings @Inject constructor(@ApplicationContext private val con // System var isDocked by sharedPreferences(context, true) var usernameValue by sharedPreferences(context, context.getString(R.string.username_default)) + var profilePictureValue by sharedPreferences(context, "") var systemLanguage by sharedPreferences(context, 1) var systemRegion by sharedPreferences(context, -1) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3c3c2fed..176f539f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -47,6 +47,7 @@ The system will emulate being in docked mode Username @string/app_name + Profile picture System language System region diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 07506875..ee57b9e6 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -70,6 +70,9 @@ app:key="username_value" app:limit="31" app:title="@string/username" /> +